-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
which.go
126 lines (105 loc) · 2.76 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
125
126
// 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 (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/spf13/afero"
)
// 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 {
return which(afero.NewOsFs(), program...)
}
func which(fs afero.Fs, program ...string) string {
for _, prog := range program {
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(fs, candidate) {
return candidate
}
}
}
return ""
}
// All returns all instances of the executable program(s), instead of just the
// first one.
func All(program ...string) []string {
return all(afero.NewOsFs(), program...)
}
func all(fs afero.Fs, program ...string) []string {
out := []string{}
for _, prog := range program {
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(fs, 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 {
return found(afero.NewOsFs(), program...)
}
func found(fs afero.Fs, program ...string) bool {
count := 0
for _, prog := range program {
count = 0
for _, p := range getPath() {
candidate := filepath.Join(p, prog)
if isExec(fs, candidate) {
count++
}
}
if count == 0 {
return false
}
}
return count > 0
}
func getPath() []string {
pathVar := os.Getenv("PATH")
if pathVar == "" && runtime.GOOS == "windows" {
for i, k := range keys(os.Environ()) {
if strings.ToLower(k) == "path" {
pathVar = os.Environ()[i]
break
}
}
}
return strings.Split(pathVar, string(os.PathListSeparator))
}
func keys(env []string) []string {
out := make([]string, len(env))
for i, v := range env {
parts := strings.SplitN(v, "=", 2)
out[i] = parts[0]
}
return out
}
// isExec returns true when the file at the given path is a regular file with
// the execute bit set (if on UNIX)
func isExec(fs afero.Fs, path string) bool {
fi, err := fs.Stat(path)
switch {
case os.IsNotExist(err):
return false
case fi.IsDir():
return false
case fi.Mode()&0111 != 0:
return true
}
// Windows filesystems have no execute bit...
return runtime.GOOS == "windows"
}