129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package jsh
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// FieldsN slices s into substrings after each instance of a whitespace
|
|
// character (according to unicode.IsSpace) and returns a slice of those
|
|
// substrings. The slice's length is guaranteed to be at most maxN, and
|
|
// all leftover fields are put into the last substring.
|
|
func FieldsN(s string, maxN int) []string {
|
|
// First count the fields.
|
|
n := 0
|
|
inField := false
|
|
for _, rune := range s {
|
|
wasInField := inField
|
|
inField = !unicode.IsSpace(rune)
|
|
if inField && !wasInField {
|
|
n++
|
|
}
|
|
}
|
|
n = int(math.Min(float64(n), float64(maxN)))
|
|
|
|
// Now create them.
|
|
a := make([]string, n)
|
|
na := 0
|
|
fieldStart := -1 // Set to -1 when looking for start of field.
|
|
for i, rune := range s {
|
|
if unicode.IsSpace(rune) && na+1 < maxN {
|
|
if fieldStart >= 0 {
|
|
a[na] = s[fieldStart:i]
|
|
na++
|
|
fieldStart = -1
|
|
}
|
|
} else if fieldStart == -1 {
|
|
fieldStart = i
|
|
}
|
|
}
|
|
if fieldStart >= 0 { // Last field might end at EOF.
|
|
a[na] = s[fieldStart:]
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Falls back to procps-ng passing the space-separated arguments in the given
|
|
// args slice. If args is nil, we default to the arguments passed to the command
|
|
// line using os.Args
|
|
func FallbackWithArgs(program string, args []string) *[]byte {
|
|
if args == nil {
|
|
args = os.Args[1:]
|
|
}
|
|
executable, err := findExecutable(program)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
out, err := exec.Command(executable, args...).Output()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &out
|
|
}
|
|
|
|
func Fallback(program string) *[]byte {
|
|
return FallbackWithArgs(program, nil)
|
|
}
|
|
|
|
// Finds a given executable on the system path, returning the absolute path the program if found, and an error
|
|
// if it's not found
|
|
func findExecutable(programName string) (string, error) {
|
|
// First, find our binary location
|
|
binLocWithName, err := os.Readlink("/proc/self/exe")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Get our binary name and remove it from the binary path, leaving just our binary location
|
|
// Also trim the extra path separator
|
|
binName := os.Args[0]
|
|
binLoc := strings.TrimSuffix(binLocWithName, binName)
|
|
binLoc = strings.TrimSuffix(binLoc, string(os.PathSeparator))
|
|
|
|
// Get the system path and split it by the system path separator
|
|
pathString := os.Getenv("PATH")
|
|
path := strings.Split(pathString, string(os.PathListSeparator))
|
|
|
|
// Loop until we find our location on the path, so we know where to start looking after for the
|
|
// next implementation of the given program name
|
|
var foundPath bool
|
|
var pathNum int
|
|
for index, pathEl := range path {
|
|
// Trim the path separator from this element, if it has it
|
|
pathEl = strings.TrimSuffix(pathEl, string(os.PathSeparator))
|
|
if pathEl == binLoc {
|
|
foundPath = true
|
|
pathNum = index
|
|
break
|
|
}
|
|
}
|
|
|
|
// If we found our path on the real path, then search the remaining path elements. Otherwise, search all elements
|
|
if foundPath {
|
|
return searchPath(programName, path[pathNum+1:len(path)-1])
|
|
} else {
|
|
return searchPath(programName, path)
|
|
}
|
|
}
|
|
|
|
// Searches the given set of paths for a the given program, returning when it finds it or returning an error if the
|
|
// program is not found
|
|
func searchPath(programName string, path []string) (string, error) {
|
|
// Loop through each path element, test for the existance of the file, and then return if we find it
|
|
for _, pathEl := range path {
|
|
fullProgram := pathEl + string(os.PathSeparator) + programName
|
|
_, err := os.Stat(fullProgram)
|
|
if err == nil {
|
|
return fullProgram, nil
|
|
}
|
|
}
|
|
|
|
return "", errors.New(fmt.Sprintf("Could not find program %s on the PATH: %s", programName, path))
|
|
}
|