Skip to content

Commit dbf036f

Browse files
committed
refactor: simplify brew.sh with native macOS tools
- resolve_path: use realpath (macOS 12.3+) instead of python3/perl - deduplicate tokens with sort -u instead of manual loop - reuse _extract_cask_token_from_path in symlink check - simplify brew_uninstall_cask boolean logic Reduces 88 lines (281 → 193)
1 parent b82dd42 commit dbf036f

File tree

1 file changed

+39
-127
lines changed

1 file changed

+39
-127
lines changed

lib/uninstall/brew.sh

Lines changed: 39 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,18 @@ readonly MOLE_BREW_UNINSTALL_LOADED=1
1515
# Returns: Absolute resolved path, or empty string on failure
1616
resolve_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

Comments
 (0)