|
@@ -15,38 +15,27 @@ func DontSearch(exe string) bool {
|
|
|
strings.ContainsRune(exe, '/')
|
|
|
}
|
|
|
|
|
|
-// IsExecutable determines whether path refers to an executable file.
|
|
|
-func IsExecutable(path string) bool {
|
|
|
- fi, err := os.Stat(path)
|
|
|
- if err != nil {
|
|
|
- return false
|
|
|
- }
|
|
|
- fm := fi.Mode()
|
|
|
- return !fm.IsDir() && (fm&0111 != 0)
|
|
|
-}
|
|
|
-
|
|
|
-// EachExternal calls f for each name that can resolve to an external command.
|
|
|
-//
|
|
|
-// BUG: EachExternal may generate the same command multiple command it it
|
|
|
-// appears in multiple directories in PATH.
|
|
|
+// EachExternal calls f for each executable file found while scanning the directories of $E:PATH.
|
|
|
//
|
|
|
-// BUG: EachExternal doesn't work on Windows since it relies on the execution
|
|
|
-// permission bit, which doesn't exist on Windows.
|
|
|
+// NOTE: EachExternal may generate the same command multiple times; once for each time it appears in
|
|
|
+// $E:PATH. That is, no deduplication of the files found by scanning $E:PATH is performed.
|
|
|
func EachExternal(f func(string)) {
|
|
|
for _, dir := range searchPaths() {
|
|
|
files, err := os.ReadDir(dir)
|
|
|
if err != nil {
|
|
|
+ // In practice this rarely happens. There isn't much we can reasonably do when it does
|
|
|
+ // happen other than silently ignore the invalid directory.
|
|
|
continue
|
|
|
}
|
|
|
for _, file := range files {
|
|
|
- info, err := file.Info()
|
|
|
- if err == nil && !info.IsDir() && (info.Mode()&0111 != 0) {
|
|
|
- f(file.Name())
|
|
|
+ stat, err := file.Info()
|
|
|
+ if err == nil && IsExecutable(stat) {
|
|
|
+ f(stat.Name())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func searchPaths() []string {
|
|
|
- return strings.Split(os.Getenv(env.PATH), ":")
|
|
|
+ return strings.Split(os.Getenv(env.PATH), string(filepath.ListSeparator))
|
|
|
}
|