-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
which.go
124 lines (102 loc) · 2.6 KB
/
which.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Package which - locates executable files in the current path. A cross-platform
// implementation of the `which(1)` command.
//
// This allows finding programs by searching the current `PATH` environment
// variable without needing to shell out to the operating system's `which` command.
package which
import (
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
)
// Which locates executable program(s) in the user's path. If more than one
// occurrence is found, the first will be returned. Unlike the UNIX which(1)
// command, even if multiple programs are given as input, only the first-found
// will be returned. If none of the programs are found, the empty string is
// returned.
func Which(program ...string) string {
for _, prog := range program {
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(candidate) {
return candidate
}
}
}
return ""
}
// All returns all instances of the executable program(s), instead of just the
// first one.
func All(program ...string) []string {
out := []string{}
for _, prog := range program {
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(candidate) {
out = append(out, candidate)
}
}
}
return out
}
// Found returns true if all of the given executable program(s) are found, false
// if one or more are not found.
func Found(program ...string) bool {
count := 0
for _, prog := range program {
count = 0
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(candidate) {
count++
}
}
if count == 0 {
return false
}
}
return count > 0
}
func getPath() []string {
return strings.Split(os.Getenv("PATH"), string(os.PathListSeparator))
}
//nolint:gochecknoglobals
var testFS fs.FS
// fsysFor returns the filesystem and the relative path for an absolute path.
// Handles Windows by not assuming all paths are rooted at /
func fsysFor(path string) (fs.FS, string) {
//nolint:gomnd
parts := strings.SplitAfterN(path, "/", 2)
root := parts[0]
if root == "" {
root = "/"
}
if len(parts) > 1 {
path = parts[1]
}
if path == "" {
path = "."
}
if testFS != nil {
return testFS, path
}
return os.DirFS(root), path
}
// isExec returns true when the file at an absolute path is a regular file with
// the execute bit set (if on UNIX)
func isExec(p string) bool {
fsys, path := fsysFor(filepath.ToSlash(p))
fi, err := fs.Stat(fsys, path)
switch {
case err != nil:
return false
case fi.IsDir():
return false
case fi.Mode()&0o111 != 0:
return true
}
// Windows filesystems have no execute bit...
return runtime.GOOS == "windows"
}