forked from glycerine/zygomys
/
system.go
125 lines (111 loc) · 2.94 KB
/
system.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
package zygo
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
)
var ShellCmd string = "/bin/bash"
func init() {
SetShellCmd()
}
// set ShellCmd as used by SystemFunction
func SetShellCmd() {
if runtime.GOOS == "windows" {
ShellCmd = os.Getenv("COMSPEC")
return
}
try := []string{"/usr/bin/bash"}
if !FileExists(ShellCmd) {
for i := range try {
b := try[i]
if FileExists(b) {
ShellCmd = b
return
}
}
}
}
// sys is a builder. shell out, return the combined output.
func SystemBuilder(env *Zlisp, name string, args []Sexp) (Sexp, error) {
//P("SystemBuilder called with args='%#v'", args)
return SystemFunction(env, name, args)
}
func SystemFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) == 0 {
return SexpNull, WrongNargs
}
flat, err := flattenToWordsHelper(args)
if err != nil {
return SexpNull, fmt.Errorf("flatten on '%#v' failed with error '%s'", args, err)
}
if len(flat) == 0 {
return SexpNull, WrongNargs
}
joined := strings.Join(flat, " ")
cmd := ShellCmd
var out []byte
if runtime.GOOS == "windows" {
out, err = exec.Command(cmd, "/c", joined).CombinedOutput()
} else {
out, err = exec.Command(cmd, "-c", joined).CombinedOutput()
}
if err != nil {
return SexpNull, fmt.Errorf("error from command: '%s'. Output:'%s'", err, string(Chomp(out)))
}
return &SexpStr{S: string(Chomp(out))}, nil
}
// given strings/lists of strings with possible whitespace
// flatten out to a array of SexpStr with no internal whitespace,
// suitable for passing along to (system) / exec.Command()
func FlattenToWordsFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) == 0 {
return SexpNull, WrongNargs
}
stringArgs, err := flattenToWordsHelper(args)
if err != nil {
return SexpNull, err
}
// Now convert to []Sexp{SexpStr}
res := make([]Sexp, len(stringArgs))
for i := range stringArgs {
res[i] = &SexpStr{S: stringArgs[i]}
}
return env.NewSexpArray(res), nil
}
func flattenToWordsHelper(args []Sexp) ([]string, error) {
stringArgs := []string{}
for i := range args {
switch c := args[i].(type) {
case *SexpStr:
many := strings.Split(c.S, " ")
stringArgs = append(stringArgs, many...)
case *SexpSymbol:
stringArgs = append(stringArgs, c.name)
case *SexpPair:
carry, err := ListToArray(c)
if err != nil {
return []string{}, fmt.Errorf("tried to convert list of strings to array but failed with error '%s'. Input was type %T / val = '%#v'", err, c, c)
}
moreWords, err := flattenToWordsHelper(carry)
if err != nil {
return []string{}, err
}
stringArgs = append(stringArgs, moreWords...)
default:
return []string{}, fmt.Errorf("arguments to system must be strings; instead we have %T / val = '%#v'", c, c)
}
} // end i over args
// INVAR: stringArgs has our flattened list.
return stringArgs, nil
}
func Chomp(by []byte) []byte {
if len(by) > 0 {
n := len(by)
if by[n-1] == '\n' {
return by[:n-1]
}
}
return by
}