@@ -15,49 +15,18 @@ readonly MOLE_BREW_UNINSTALL_LOADED=1
1515# Returns: Absolute resolved path, or empty string on failure
1616resolve_path () {
1717 local p=" $1 "
18+ [[ -e " $p " ]] || return 1
1819
19- # Prefer realpath if available (GNU coreutils)
20- if command -v realpath > /dev/null 2>&1 ; then
21- realpath " $p " 2> /dev/null && return 0
22- fi
23-
24- # macOS fallback: use python3 (almost always available)
25- if command -v python3 > /dev/null 2>&1 ; then
26- python3 -c " import os,sys; print(os.path.realpath(sys.argv[1]))" " $p " 2> /dev/null && return 0
27- fi
28-
29- # Last resort: perl (available on macOS)
30- if command -v perl > /dev/null 2>&1 ; then
31- perl -MCwd -e ' print Cwd::realpath($ARGV[0])' " $p " 2> /dev/null && return 0
32- fi
33-
34- # Final fallback: if symlink, try to make readlink output absolute
35- if [[ -L " $p " ]]; then
36- local target
37- target=$( readlink " $p " 2> /dev/null) || return 1
38- # If target is relative, prepend the directory of the symlink
39- if [[ " $target " != /* ]]; then
40- local dir
41- dir=$( cd -P " $( dirname " $p " ) " 2> /dev/null && pwd) || return 1
42- target=" $dir /$target "
43- fi
44- # Normalize by resolving the directory component
45- local target_dir target_base
46- target_dir=$( cd -P " $( dirname " $target " ) " 2> /dev/null && pwd) || {
47- echo " $target "
48- return 0
49- }
50- target_base=$( basename " $target " )
51- echo " $target_dir /$target_base "
20+ # macOS 12.3+ and Linux have realpath
21+ if realpath " $p " 2> /dev/null; then
5222 return 0
5323 fi
5424
55- # Not a symlink, return as-is if it exists
56- if [[ -e " $p " ]]; then
57- echo " $p "
58- return 0
59- fi
60- return 1
25+ # Fallback: use cd -P to resolve directory, then append basename
26+ local dir base
27+ dir=$( cd -P " $( dirname " $p " ) " 2> /dev/null && pwd) || return 1
28+ base=$( basename " $p " )
29+ echo " $dir /$base "
6130}
6231
6332# Check if Homebrew is installed and accessible
@@ -112,81 +81,54 @@ _detect_cask_via_caskroom_search() {
11281 [[ -z " $app_bundle_name " ]] && return 1
11382
11483 local -a tokens=()
115- local -a uniq=()
116- local room match token t u seen
84+ local room match token
11785
11886 for room in " /opt/homebrew/Caskroom" " /usr/local/Caskroom" ; do
11987 [[ -d " $room " ]] || continue
12088 while IFS= read -r match; do
12189 [[ -n " $match " ]] || continue
12290 token=$( _extract_cask_token_from_path " $match " 2> /dev/null) || continue
123- [[ -n " $token " ]] || continue
124- tokens+=(" $token " )
91+ [[ -n " $token " ]] && tokens+=(" $token " )
12592 done < <( find " $room " -maxdepth 3 -name " $app_bundle_name " 2> /dev/null)
12693 done
12794
128- # Deduplicate tokens
129- for t in " ${tokens[@]+" ${tokens[@]} " } " ; do
130- seen=false
131- for u in " ${uniq[@]+" ${uniq[@]} " } " ; do
132- [[ " $u " == " $t " ]] && { seen=true; break ; }
133- done
134- [[ " $seen " == " false" ]] && uniq+=(" $t " )
135- done
95+ # Deduplicate and check count
96+ local -a uniq
97+ IFS=$' \n ' read -r -d ' ' -a uniq < <( printf ' %s\n' " ${tokens[@]} " | sort -u && printf ' \0' ) || true
13698
13799 # Only succeed if exactly one unique token found and it's installed
138- if (( ${# uniq[@]} == 1 )) ; then
139- local candidate=" ${uniq[0]} "
140- HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -qxF " $candidate " || return 1
141- echo " $candidate "
100+ if (( ${# uniq[@]} == 1 )) && [[ -n " ${uniq[0]} " ]]; then
101+ HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -qxF " ${uniq[0]} " || return 1
102+ echo " ${uniq[0]} "
142103 return 0
143104 fi
144105
145106 return 1
146107}
147108
148- # Stage 3: Check if app_path is a direct symlink to Caskroom (simpler readlink check)
149- # Redundant with stage 1 in most cases, but kept as fallback
109+ # Stage 3: Check if app_path is a direct symlink to Caskroom
150110_detect_cask_via_symlink_check () {
151111 local app_path=" $1 "
152112 [[ -L " $app_path " ]] || return 1
153113
154114 local target
155115 target=$( readlink " $app_path " 2> /dev/null) || return 1
156-
157- for room in " /opt/homebrew/Caskroom" " /usr/local/Caskroom" ; do
158- if [[ " $target " == " $room /" * ]]; then
159- local relative=" ${target# " $room " / } "
160- local token=" ${relative%%/* } "
161- if [[ -n " $token " && " $token " =~ ^[a-z0-9][a-z0-9-]* $ ]]; then
162- echo " $token "
163- return 0
164- fi
165- fi
166- done
167-
168- return 1
116+ _extract_cask_token_from_path " $target "
169117}
170118
171- # Stage 4: Query brew list --cask and verify with brew info
172- # Slowest but catches edge cases where app was moved/renamed
119+ # Stage 4: Query brew list --cask and verify with brew info (slowest fallback)
173120_detect_cask_via_brew_list () {
174121 local app_path=" $1 "
175122 local app_bundle_name=" $2 "
123+ local app_name_lower
124+ app_name_lower=$( echo " ${app_bundle_name% .app} " | LC_ALL=C tr ' [:upper:]' ' [:lower:]' )
176125
177- local app_name_only=" ${app_bundle_name% .app} "
178126 local cask_name
179- cask_name=$( HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -Fix " $( echo " $app_name_only " | LC_ALL=C tr ' [:upper:]' ' [:lower:]' ) " || echo " " )
180-
181- if [[ -n " $cask_name " ]]; then
182- # Verify this cask actually owns this app path
183- if HOMEBREW_NO_ENV_HINTS=1 brew info --cask " $cask_name " 2> /dev/null | grep -qF " $app_path " ; then
184- echo " $cask_name "
185- return 0
186- fi
187- fi
127+ cask_name=$( HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -Fix " $app_name_lower " ) || return 1
188128
189- return 1
129+ # Verify this cask actually owns this app path
130+ HOMEBREW_NO_ENV_HINTS=1 brew info --cask " $cask_name " 2> /dev/null | grep -qF " $app_path " || return 1
131+ echo " $cask_name "
190132}
191133
192134# Get Homebrew cask name for an app
@@ -228,54 +170,24 @@ brew_uninstall_cask() {
228170
229171 debug_log " Attempting brew uninstall --cask $cask_name "
230172
231- # Suppress hints, auto-update, and ensure non-interactive
232- export HOMEBREW_NO_ENV_HINTS=1
233- export HOMEBREW_NO_AUTO_UPDATE=1
234- export NONINTERACTIVE=1
235-
236- # Run uninstall with timeout (cask uninstalls can hang on prompts)
237- local output
238- local uninstall_succeeded=false
239- if output=$( run_with_timeout 120 brew uninstall --cask " $cask_name " 2>&1 ) ; then
240- debug_log " brew uninstall --cask $cask_name completed successfully"
241- uninstall_succeeded=true
242- else
243- local exit_code=$?
244- debug_log " brew uninstall --cask $cask_name exited with code $exit_code : $output "
173+ # Run uninstall with timeout (suppress hints/auto-update)
174+ local uninstall_ok=false
175+ if HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_AUTO_UPDATE=1 NONINTERACTIVE=1 \
176+ run_with_timeout 120 brew uninstall --cask " $cask_name " 2>&1 ; then
177+ uninstall_ok=true
245178 fi
246179
247- # Check current state
248- local cask_still_installed=false
249- if HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -qxF " $cask_name " ; then
250- cask_still_installed=true
251- fi
252-
253- local app_still_exists=false
254- if [[ -n " $app_path " && -e " $app_path " ]]; then
255- app_still_exists=true
256- fi
180+ # Verify removal
181+ local cask_gone=true app_gone=true
182+ HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2> /dev/null | grep -qxF " $cask_name " && cask_gone=false
183+ [[ -n " $app_path " && -e " $app_path " ]] && app_gone=false
257184
258- # Success cases:
259- # 1. Uninstall succeeded and cask/app are gone
260- # 2. Uninstall failed but cask wasn't installed anyway (idempotent)
261- if [[ " $uninstall_succeeded " == " true" ]]; then
262- if [[ " $cask_still_installed " == " true" ]]; then
263- debug_log " Cask '$cask_name ' still in brew list after successful uninstall"
264- return 1
265- fi
266- if [[ " $app_still_exists " == " true" ]]; then
267- debug_log " App still exists at '$app_path ' after brew uninstall"
268- return 1
269- fi
185+ # Success: uninstall worked and both are gone, or already uninstalled
186+ if $cask_gone && $app_gone ; then
270187 debug_log " Successfully uninstalled cask '$cask_name '"
271188 return 0
272- else
273- # Uninstall command failed - only succeed if already fully uninstalled
274- if [[ " $cask_still_installed " == " false" && " $app_still_exists " == " false" ]]; then
275- debug_log " Cask '$cask_name ' was already uninstalled"
276- return 0
277- fi
278- debug_log " brew uninstall failed and cask/app still present"
279- return 1
280189 fi
190+
191+ debug_log " brew uninstall failed: cask_gone=$cask_gone app_gone=$app_gone "
192+ return 1
281193}
0 commit comments