mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 18:34:46 +00:00
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)
This commit is contained in:
@@ -15,49 +15,18 @@ readonly MOLE_BREW_UNINSTALL_LOADED=1
|
||||
# Returns: Absolute resolved path, or empty string on failure
|
||||
resolve_path() {
|
||||
local p="$1"
|
||||
[[ -e "$p" ]] || return 1
|
||||
|
||||
# Prefer realpath if available (GNU coreutils)
|
||||
if command -v realpath > /dev/null 2>&1; then
|
||||
realpath "$p" 2> /dev/null && return 0
|
||||
fi
|
||||
|
||||
# macOS fallback: use python3 (almost always available)
|
||||
if command -v python3 > /dev/null 2>&1; then
|
||||
python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$p" 2> /dev/null && return 0
|
||||
fi
|
||||
|
||||
# Last resort: perl (available on macOS)
|
||||
if command -v perl > /dev/null 2>&1; then
|
||||
perl -MCwd -e 'print Cwd::realpath($ARGV[0])' "$p" 2> /dev/null && return 0
|
||||
fi
|
||||
|
||||
# Final fallback: if symlink, try to make readlink output absolute
|
||||
if [[ -L "$p" ]]; then
|
||||
local target
|
||||
target=$(readlink "$p" 2>/dev/null) || return 1
|
||||
# If target is relative, prepend the directory of the symlink
|
||||
if [[ "$target" != /* ]]; then
|
||||
local dir
|
||||
dir=$(cd -P "$(dirname "$p")" 2>/dev/null && pwd) || return 1
|
||||
target="$dir/$target"
|
||||
fi
|
||||
# Normalize by resolving the directory component
|
||||
local target_dir target_base
|
||||
target_dir=$(cd -P "$(dirname "$target")" 2>/dev/null && pwd) || {
|
||||
echo "$target"
|
||||
return 0
|
||||
}
|
||||
target_base=$(basename "$target")
|
||||
echo "$target_dir/$target_base"
|
||||
# macOS 12.3+ and Linux have realpath
|
||||
if realpath "$p" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Not a symlink, return as-is if it exists
|
||||
if [[ -e "$p" ]]; then
|
||||
echo "$p"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
# Fallback: use cd -P to resolve directory, then append basename
|
||||
local dir base
|
||||
dir=$(cd -P "$(dirname "$p")" 2>/dev/null && pwd) || return 1
|
||||
base=$(basename "$p")
|
||||
echo "$dir/$base"
|
||||
}
|
||||
|
||||
# Check if Homebrew is installed and accessible
|
||||
@@ -112,81 +81,54 @@ _detect_cask_via_caskroom_search() {
|
||||
[[ -z "$app_bundle_name" ]] && return 1
|
||||
|
||||
local -a tokens=()
|
||||
local -a uniq=()
|
||||
local room match token t u seen
|
||||
local room match token
|
||||
|
||||
for room in "/opt/homebrew/Caskroom" "/usr/local/Caskroom"; do
|
||||
[[ -d "$room" ]] || continue
|
||||
while IFS= read -r match; do
|
||||
[[ -n "$match" ]] || continue
|
||||
token=$(_extract_cask_token_from_path "$match" 2>/dev/null) || continue
|
||||
[[ -n "$token" ]] || continue
|
||||
tokens+=("$token")
|
||||
[[ -n "$token" ]] && tokens+=("$token")
|
||||
done < <(find "$room" -maxdepth 3 -name "$app_bundle_name" 2>/dev/null)
|
||||
done
|
||||
|
||||
# Deduplicate tokens
|
||||
for t in "${tokens[@]+"${tokens[@]}"}"; do
|
||||
seen=false
|
||||
for u in "${uniq[@]+"${uniq[@]}"}"; do
|
||||
[[ "$u" == "$t" ]] && { seen=true; break; }
|
||||
done
|
||||
[[ "$seen" == "false" ]] && uniq+=("$t")
|
||||
done
|
||||
# Deduplicate and check count
|
||||
local -a uniq
|
||||
IFS=$'\n' read -r -d '' -a uniq < <(printf '%s\n' "${tokens[@]}" | sort -u && printf '\0') || true
|
||||
|
||||
# Only succeed if exactly one unique token found and it's installed
|
||||
if ((${#uniq[@]} == 1)); then
|
||||
local candidate="${uniq[0]}"
|
||||
HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2>/dev/null | grep -qxF "$candidate" || return 1
|
||||
echo "$candidate"
|
||||
if ((${#uniq[@]} == 1)) && [[ -n "${uniq[0]}" ]]; then
|
||||
HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2>/dev/null | grep -qxF "${uniq[0]}" || return 1
|
||||
echo "${uniq[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Stage 3: Check if app_path is a direct symlink to Caskroom (simpler readlink check)
|
||||
# Redundant with stage 1 in most cases, but kept as fallback
|
||||
# Stage 3: Check if app_path is a direct symlink to Caskroom
|
||||
_detect_cask_via_symlink_check() {
|
||||
local app_path="$1"
|
||||
[[ -L "$app_path" ]] || return 1
|
||||
|
||||
local target
|
||||
target=$(readlink "$app_path" 2>/dev/null) || return 1
|
||||
|
||||
for room in "/opt/homebrew/Caskroom" "/usr/local/Caskroom"; do
|
||||
if [[ "$target" == "$room/"* ]]; then
|
||||
local relative="${target#"$room"/}"
|
||||
local token="${relative%%/*}"
|
||||
if [[ -n "$token" && "$token" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
||||
echo "$token"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
_extract_cask_token_from_path "$target"
|
||||
}
|
||||
|
||||
# Stage 4: Query brew list --cask and verify with brew info
|
||||
# Slowest but catches edge cases where app was moved/renamed
|
||||
# Stage 4: Query brew list --cask and verify with brew info (slowest fallback)
|
||||
_detect_cask_via_brew_list() {
|
||||
local app_path="$1"
|
||||
local app_bundle_name="$2"
|
||||
local app_name_lower
|
||||
app_name_lower=$(echo "${app_bundle_name%.app}" | LC_ALL=C tr '[:upper:]' '[:lower:]')
|
||||
|
||||
local app_name_only="${app_bundle_name%.app}"
|
||||
local cask_name
|
||||
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 "")
|
||||
cask_name=$(HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2>/dev/null | grep -Fix "$app_name_lower") || return 1
|
||||
|
||||
if [[ -n "$cask_name" ]]; then
|
||||
# Verify this cask actually owns this app path
|
||||
if HOMEBREW_NO_ENV_HINTS=1 brew info --cask "$cask_name" 2> /dev/null | grep -qF "$app_path"; then
|
||||
echo "$cask_name"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
# Verify this cask actually owns this app path
|
||||
HOMEBREW_NO_ENV_HINTS=1 brew info --cask "$cask_name" 2>/dev/null | grep -qF "$app_path" || return 1
|
||||
echo "$cask_name"
|
||||
}
|
||||
|
||||
# Get Homebrew cask name for an app
|
||||
@@ -228,54 +170,24 @@ brew_uninstall_cask() {
|
||||
|
||||
debug_log "Attempting brew uninstall --cask $cask_name"
|
||||
|
||||
# Suppress hints, auto-update, and ensure non-interactive
|
||||
export HOMEBREW_NO_ENV_HINTS=1
|
||||
export HOMEBREW_NO_AUTO_UPDATE=1
|
||||
export NONINTERACTIVE=1
|
||||
|
||||
# Run uninstall with timeout (cask uninstalls can hang on prompts)
|
||||
local output
|
||||
local uninstall_succeeded=false
|
||||
if output=$(run_with_timeout 120 brew uninstall --cask "$cask_name" 2>&1); then
|
||||
debug_log "brew uninstall --cask $cask_name completed successfully"
|
||||
uninstall_succeeded=true
|
||||
else
|
||||
local exit_code=$?
|
||||
debug_log "brew uninstall --cask $cask_name exited with code $exit_code: $output"
|
||||
# Run uninstall with timeout (suppress hints/auto-update)
|
||||
local uninstall_ok=false
|
||||
if HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_AUTO_UPDATE=1 NONINTERACTIVE=1 \
|
||||
run_with_timeout 120 brew uninstall --cask "$cask_name" 2>&1; then
|
||||
uninstall_ok=true
|
||||
fi
|
||||
|
||||
# Check current state
|
||||
local cask_still_installed=false
|
||||
if HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2>/dev/null | grep -qxF "$cask_name"; then
|
||||
cask_still_installed=true
|
||||
fi
|
||||
# Verify removal
|
||||
local cask_gone=true app_gone=true
|
||||
HOMEBREW_NO_ENV_HINTS=1 brew list --cask 2>/dev/null | grep -qxF "$cask_name" && cask_gone=false
|
||||
[[ -n "$app_path" && -e "$app_path" ]] && app_gone=false
|
||||
|
||||
local app_still_exists=false
|
||||
if [[ -n "$app_path" && -e "$app_path" ]]; then
|
||||
app_still_exists=true
|
||||
fi
|
||||
|
||||
# Success cases:
|
||||
# 1. Uninstall succeeded and cask/app are gone
|
||||
# 2. Uninstall failed but cask wasn't installed anyway (idempotent)
|
||||
if [[ "$uninstall_succeeded" == "true" ]]; then
|
||||
if [[ "$cask_still_installed" == "true" ]]; then
|
||||
debug_log "Cask '$cask_name' still in brew list after successful uninstall"
|
||||
return 1
|
||||
fi
|
||||
if [[ "$app_still_exists" == "true" ]]; then
|
||||
debug_log "App still exists at '$app_path' after brew uninstall"
|
||||
return 1
|
||||
fi
|
||||
# Success: uninstall worked and both are gone, or already uninstalled
|
||||
if $cask_gone && $app_gone; then
|
||||
debug_log "Successfully uninstalled cask '$cask_name'"
|
||||
return 0
|
||||
else
|
||||
# Uninstall command failed - only succeed if already fully uninstalled
|
||||
if [[ "$cask_still_installed" == "false" && "$app_still_exists" == "false" ]]; then
|
||||
debug_log "Cask '$cask_name' was already uninstalled"
|
||||
return 0
|
||||
fi
|
||||
debug_log "brew uninstall failed and cask/app still present"
|
||||
return 1
|
||||
fi
|
||||
|
||||
debug_log "brew uninstall failed: cask_gone=$cask_gone app_gone=$app_gone"
|
||||
return 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user