-
Notifications
You must be signed in to change notification settings - Fork 140
/
sh.go
204 lines (174 loc) · 4.3 KB
/
sh.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
Package go-sh is intended to make shell call with golang more easily.
Some usage is more similar to os/exec, eg: Run(), Output(), Command(name, args...)
But with these similar function, pipe is added in and this package also got shell-session support.
Why I love golang so much, because the usage of golang is simple, but the power is unlimited. I want to make this pakcage got the sample style like golang.
// just like os/exec
sh.Command("echo", "hello").Run()
// support pipe
sh.Command("echo", "hello").Command("wc", "-c").Run()
// create a session to store dir and env
sh.NewSession().SetDir("/").Command("pwd")
// shell buildin command - "test"
sh.Test("dir", "mydir")
// like shell call: (cd /; pwd)
sh.Command("pwd", sh.Dir("/")) same with sh.Command(sh.Dir("/"), "pwd")
// output to json and xml easily
v := map[string] int {}
err = sh.Command("echo", `{"number": 1}`).UnmarshalJSON(&v)
*/
package sh
import (
"fmt"
"io"
"os"
"os/exec"
"reflect"
"strings"
"time"
"github.com/codegangsta/inject"
)
type Dir string
type Session struct {
inj inject.Injector
alias map[string][]string
cmds []*exec.Cmd
dir Dir
started bool
Env map[string]string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
ShowCMD bool // enable for debug
timeout time.Duration
// additional pipe options
PipeFail bool // returns error of rightmost no-zero command
PipeStdErrors bool // combine std errors of all pipe commands
}
func (s *Session) writePrompt(args ...interface{}) {
var ps1 = fmt.Sprintf("[golang-sh]$")
args = append([]interface{}{ps1}, args...)
fmt.Fprintln(s.Stderr, args...)
}
func NewSession() *Session {
env := make(map[string]string)
for _, key := range []string{"PATH"} {
env[key] = os.Getenv(key)
}
s := &Session{
inj: inject.New(),
alias: make(map[string][]string),
dir: Dir(""),
Stdin: strings.NewReader(""),
Stdout: os.Stdout,
Stderr: os.Stderr,
Env: env,
}
return s
}
func InteractiveSession() *Session {
s := NewSession()
s.SetStdin(os.Stdin)
return s
}
func Command(name string, a ...interface{}) *Session {
s := NewSession()
return s.Command(name, a...)
}
func Echo(in string) *Session {
s := NewSession()
return s.SetInput(in)
}
func (s *Session) Alias(alias, cmd string, args ...string) {
v := []string{cmd}
v = append(v, args...)
s.alias[alias] = v
}
func (s *Session) Command(name string, a ...interface{}) *Session {
var args = make([]string, 0)
var sType = reflect.TypeOf("")
// init cmd, args, dir, envs
// if not init, program may panic
s.inj.Map(name).Map(args).Map(s.dir).Map(map[string]string{})
for _, v := range a {
switch reflect.TypeOf(v) {
case sType:
args = append(args, v.(string))
default:
s.inj.Map(v)
}
}
if len(args) != 0 {
s.inj.Map(args)
}
s.inj.Invoke(s.appendCmd)
return s
}
// combine Command and Run
func (s *Session) Call(name string, a ...interface{}) error {
return s.Command(name, a...).Run()
}
/*
func (s *Session) Exec(cmd string, args ...string) error {
return s.Call(cmd, args)
}
*/
func (s *Session) SetEnv(key, value string) *Session {
s.Env[key] = value
return s
}
func (s *Session) SetDir(dir string) *Session {
s.dir = Dir(dir)
return s
}
func (s *Session) SetInput(in string) *Session {
s.Stdin = strings.NewReader(in)
return s
}
func (s *Session) SetStdin(r io.Reader) *Session {
s.Stdin = r
return s
}
func (s *Session) SetTimeout(d time.Duration) *Session {
s.timeout = d
return s
}
func newEnviron(env map[string]string, inherit bool) []string { //map[string]string {
environ := make([]string, 0, len(env))
if inherit {
for _, line := range os.Environ() {
for k, _ := range env {
if strings.HasPrefix(line, k+"=") {
goto CONTINUE
}
}
environ = append(environ, line)
CONTINUE:
}
}
for k, v := range env {
environ = append(environ, k+"="+v)
}
return environ
}
func (s *Session) appendCmd(cmd string, args []string, cwd Dir, env map[string]string) {
if s.started {
s.started = false
s.cmds = make([]*exec.Cmd, 0)
}
for k, v := range s.Env {
if _, ok := env[k]; !ok {
env[k] = v
}
}
environ := newEnviron(s.Env, true) // true: inherit sys-env
v, ok := s.alias[cmd]
if ok {
cmd = v[0]
args = append(v[1:], args...)
}
c := exec.Command(cmd, args...)
c.Env = environ
c.Dir = string(cwd)
s.cmds = append(s.cmds, c)
}