@@ -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
99100detect_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