add 'exec' module

This commit is contained in:
Daniel Kang 2019-01-18 10:17:24 -08:00
parent a8d838ad3e
commit 5164d1de9f
5 changed files with 220 additions and 0 deletions

116
compiler/stdlib/exec.go Normal file
View file

@ -0,0 +1,116 @@
package stdlib
import (
"os/exec"
"github.com/d5/tengo/objects"
)
var execModule = map[string]objects.Object{
// look_path(file string) => string/error
"look_path": FuncASRSE(exec.LookPath),
// command(name string, args array(string)) => imap(cmd)
"command": &objects.UserFunction{Value: execCommand},
}
func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// combined_output() => bytes/error
"combined_output": FuncARYE(cmd.CombinedOutput),
// output() => bytes/error
"output": FuncARYE(cmd.Output),
// run() => error
"run": FuncARE(cmd.Run),
// start() => error
"start": FuncARE(cmd.Start),
// wait() => error
"wait": FuncARE(cmd.Wait),
// set_path(path string)
"set_path": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
cmd.Path = s1
return objects.UndefinedValue, nil
},
},
// set_dir(dir string)
"set_dir": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
cmd.Dir = s1
return objects.UndefinedValue, nil
},
},
// set_env(env array(string))
"set_env": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
envs, err := stringArray(args[0])
if err != nil {
return nil, err
}
cmd.Env = envs
return objects.UndefinedValue, nil
},
},
// process() => imap(process)
"process": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
return osProcessImmutableMap(cmd.Process), nil
},
},
// TODO: implement pipes
//"stderr_pipe": nil,
//"stdin_pipe": nil,
//"stdout_pipe": nil,
},
}
}
func execCommand(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
name, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}
arg, err := stringArray(args[1])
if err != nil {
return nil, err
}
res := exec.Command(name, arg...)
return execCmdImmutableMap(res), nil
}

View file

@ -95,6 +95,25 @@ func FuncARSE(fn func() (string, error)) *objects.UserFunction {
}
}
// FuncARYE transform a function of 'func() ([]byte, error)' signature
// into a user function object.
func FuncARYE(fn func() ([]byte, error)) *objects.UserFunction {
return &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 0 {
return nil, objects.ErrWrongNumArguments
}
res, err := fn()
if err != nil {
return wrapError(err), nil
}
return &objects.Bytes{Value: res}, nil
},
}
}
// FuncARF transform a function of 'func() float64' signature
// into a user function object.
func FuncARF(fn func() float64) *objects.UserFunction {

View file

@ -286,6 +286,75 @@ func TestFuncAFIRB(t *testing.T) {
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncAIRSsE(t *testing.T) {
uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) {
return []string{"foo", "bar"}, nil
})
ret, err := uf.Call(&objects.Int{Value: 10})
assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret)
uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) {
return nil, errors.New("some error")
})
ret, err = uf.Call(&objects.Int{Value: 10})
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncARB(t *testing.T) {
uf := stdlib.FuncARB(func() bool { return true })
ret, err := uf.Call()
assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret)
ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncARYE(t *testing.T) {
uf := stdlib.FuncARYE(func() ([]byte, error) {
return []byte("foo bar"), nil
})
ret, err := uf.Call()
assert.NoError(t, err)
assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret)
uf = stdlib.FuncARYE(func() ([]byte, error) {
return nil, errors.New("some error")
})
ret, err = uf.Call()
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call(objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncASRIE(t *testing.T) {
uf := stdlib.FuncASRIE(func(a string) (int, error) { return 5, nil })
ret, err := uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 5}, ret)
uf = stdlib.FuncASRIE(func(a string) (int, error) { return 0, errors.New("some error") })
ret, err = uf.Call(&objects.String{Value: "foo"})
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func TestFuncAYRIE(t *testing.T) {
uf := stdlib.FuncAYRIE(func(a []byte) (int, error) { return 5, nil })
ret, err := uf.Call(&objects.Bytes{Value: []byte("foo")})
assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 5}, ret)
uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { return 0, errors.New("some error") })
ret, err = uf.Call(&objects.Bytes{Value: []byte("foo")})
assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
ret, err = uf.Call()
assert.Equal(t, objects.ErrWrongNumArguments, err)
}
func array(elements ...objects.Object) *objects.Array {
return &objects.Array{Value: elements}
}

View file

@ -6,4 +6,5 @@ import "github.com/d5/tengo/objects"
var Modules = map[string]*objects.ImmutableMap{
"math": {Value: mathModule},
"os": {Value: osModule},
"exec": {Value: execModule},
}

View file

@ -45,6 +45,21 @@ if write_file("./temp", "foobar") {
os.remove("./temp")
`, "foobar")
// exec.command
expect(t, `
exec := import("exec")
echo := func(args) {
cmd := exec.command("echo", args)
if is_error(cmd) { return cmd.value }
output := cmd.output()
if is_error(output) { return output.value }
return output
}
out = echo(["foo", "bar"])
`, []byte("foo bar\n"))
// user modules
expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{
"mod1": `bar := func() { return 5.0 }`,