Skip to content

Commit 6b74632

Browse files
committed
refactor(capability): use type -a for multi-installation detection
Replace manual path checking with shell's `type -a` builtin: - More reliable: finds ALL binaries in PATH order - Handles PATH duplicates with seen_paths associative array - Extract classify_install_path() for cleaner code separation - Add snap detection in path classification - Simplify nvm/pipx version extraction from paths This approach is simpler and more robust than iterating over hardcoded paths, as suggested during code review.
1 parent b9ef740 commit 6b74632

1 file changed

Lines changed: 79 additions & 74 deletions

File tree

scripts/lib/capability.sh

Lines changed: 79 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -90,94 +90,99 @@ detect_install_method() {
9090
esac
9191
}
9292

93-
# Detect ALL installations of a tool across known locations
93+
# Detect ALL installations of a tool using `type -a`
94+
# This finds all binaries in PATH order, then classifies each by install method
9495
# Args: tool_name, binary_name
9596
# Returns: newline-separated list of "method:path" pairs
9697
# Example output:
9798
# cargo:/home/user/.cargo/bin/xsv
98-
# github_release_binary:/home/user/.local/bin/xsv
99+
# manual:/home/user/.local/bin/xsv
99100
detect_all_installations() {
100101
local tool="$1"
101102
local binary="${2:-$tool}"
102-
local found=()
103+
local -A seen_paths=() # associative array to track duplicates
103104

104-
# Define all known binary locations to check
105-
local locations=(
106-
"$HOME/.local/bin/$binary:github_release_binary"
107-
"$HOME/.cargo/bin/$binary:cargo"
108-
"$HOME/go/bin/$binary:go"
109-
"${GOPATH:-$HOME/go}/bin/$binary:go"
110-
"$HOME/.rbenv/shims/$binary:gem"
111-
"/usr/local/bin/$binary:brew_or_manual"
112-
"/usr/bin/$binary:apt_or_system"
113-
"/bin/$binary:apt_or_system"
114-
)
105+
# Use type -a to find all binaries in PATH
106+
while IFS= read -r line; do
107+
# Parse "binary is /path/to/binary" format
108+
local path="${line##* is }"
109+
[ -z "$path" ] && continue
110+
[ ! -x "$path" ] && continue
115111

116-
# Check nvm locations (multiple node versions)
117-
if [ -d "$HOME/.nvm/versions/node" ]; then
118-
for node_dir in "$HOME/.nvm/versions/node/"*/bin; do
119-
if [ -x "$node_dir/$binary" ]; then
120-
local node_version="${node_dir%/bin}"
121-
node_version="${node_version##*/}"
122-
found+=("npm($node_version):$node_dir/$binary")
123-
fi
124-
done
125-
fi
126-
127-
# Check pipx locations
128-
if [ -d "$HOME/.local/pipx/venvs" ]; then
129-
for venv_dir in "$HOME/.local/pipx/venvs/"*/bin; do
130-
if [ -x "$venv_dir/$binary" ]; then
131-
local pkg_name="${venv_dir%/bin}"
132-
pkg_name="${pkg_name##*/}"
133-
found+=("pipx($pkg_name):$venv_dir/$binary")
134-
fi
135-
done
136-
fi
112+
# Skip duplicates (PATH may have same dir multiple times)
113+
[ -n "${seen_paths[$path]:-}" ] && continue
114+
seen_paths[$path]=1
137115

138-
# Check standard locations
139-
for loc_spec in "${locations[@]}"; do
140-
local path="${loc_spec%%:*}"
141-
local method="${loc_spec##*:}"
116+
# Classify the installation method based on path
117+
local method
118+
method="$(classify_install_path "$tool" "$path")"
142119

143-
if [ -x "$path" ]; then
144-
# Refine method detection
145-
case "$method" in
146-
brew_or_manual)
147-
if command -v brew >/dev/null 2>&1 && brew list --formula 2>/dev/null | grep -q "^${tool}\$"; then
148-
method="brew"
149-
else
150-
method="manual"
151-
fi
152-
;;
153-
apt_or_system)
154-
if command -v dpkg >/dev/null 2>&1 && dpkg -S "$path" >/dev/null 2>&1; then
155-
method="apt"
156-
else
157-
method="system"
158-
fi
159-
;;
160-
esac
120+
echo "$method:$path"
121+
done < <(type -a "$binary" 2>/dev/null || true)
122+
}
161123

162-
# Avoid duplicates (go can have two paths pointing to same location)
163-
local already_found=""
164-
for existing in "${found[@]}"; do
165-
if [ "${existing##*:}" = "$path" ]; then
166-
already_found="true"
167-
break
168-
fi
169-
done
124+
# Classify an installation path to determine its install method
125+
# Args: tool_name, path
126+
# Returns: method name (cargo, apt, brew, npm, etc.)
127+
classify_install_path() {
128+
local tool="$1"
129+
local path="$2"
170130

171-
if [ -z "$already_found" ]; then
172-
found+=("$method:$path")
131+
case "$path" in
132+
"$HOME/.cargo/bin/"*)
133+
echo "cargo"
134+
;;
135+
"$HOME/go/bin/"*|"${GOPATH:-$HOME/go}/bin/"*)
136+
echo "go"
137+
;;
138+
"$HOME/.local/bin/"*)
139+
# Could be manual, pipx, or uv
140+
if command -v pipx >/dev/null 2>&1 && pipx list 2>/dev/null | grep -q "package $tool"; then
141+
echo "pipx"
142+
elif command -v uv >/dev/null 2>&1 && uv tool list 2>/dev/null | grep -q "^$tool\b"; then
143+
echo "uv"
144+
else
145+
echo "manual"
173146
fi
174-
fi
175-
done
176-
177-
# Output results
178-
for item in "${found[@]}"; do
179-
echo "$item"
180-
done
147+
;;
148+
"$HOME/.rbenv/shims/"*)
149+
echo "gem"
150+
;;
151+
"$HOME/.nvm/"*)
152+
# Extract node version for context
153+
local node_version="${path#$HOME/.nvm/versions/node/}"
154+
node_version="${node_version%%/*}"
155+
echo "npm($node_version)"
156+
;;
157+
"$HOME/.local/pipx/venvs/"*)
158+
local pkg="${path#$HOME/.local/pipx/venvs/}"
159+
pkg="${pkg%%/*}"
160+
echo "pipx($pkg)"
161+
;;
162+
"/home/linuxbrew/.linuxbrew/bin/"*|"/opt/homebrew/bin/"*)
163+
echo "brew"
164+
;;
165+
"/usr/local/bin/"*)
166+
if command -v brew >/dev/null 2>&1 && brew list --formula 2>/dev/null | grep -q "^${tool}\$"; then
167+
echo "brew"
168+
else
169+
echo "manual"
170+
fi
171+
;;
172+
"/usr/bin/"*|"/bin/"*)
173+
if command -v dpkg >/dev/null 2>&1 && dpkg -S "$path" >/dev/null 2>&1; then
174+
echo "apt"
175+
else
176+
echo "system"
177+
fi
178+
;;
179+
"/snap/bin/"*)
180+
echo "snap"
181+
;;
182+
*)
183+
echo "unknown"
184+
;;
185+
esac
181186
}
182187

183188
# Count number of installations for a tool

0 commit comments

Comments
 (0)