mirror of https://github.com/Hilbis/Hilbish
184 lines
4.1 KiB
Go
184 lines
4.1 KiB
Go
package snail
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"hilbish/sink"
|
|
"hilbish/util"
|
|
|
|
rt "github.com/arnodel/golua/runtime"
|
|
"github.com/arnodel/golua/lib/packagelib"
|
|
"github.com/arnodel/golua/lib/iolib"
|
|
"mvdan.cc/sh/v3/interp"
|
|
"mvdan.cc/sh/v3/syntax"
|
|
)
|
|
|
|
var snailMetaKey = rt.StringValue("hshsnail")
|
|
var Loader = packagelib.Loader{
|
|
Load: loaderFunc,
|
|
Name: "snail",
|
|
}
|
|
|
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
|
snailMeta := rt.NewTable()
|
|
snailMethods := rt.NewTable()
|
|
snailFuncs := map[string]util.LuaExport{
|
|
"run": {srun, 3, false},
|
|
}
|
|
util.SetExports(rtm, snailMethods, snailFuncs)
|
|
|
|
snailIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
arg := c.Arg(1)
|
|
val := snailMethods.Get(arg)
|
|
|
|
return c.PushingNext1(t.Runtime, val), nil
|
|
}
|
|
snailMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(snailIndex, "__index", 2, false)))
|
|
rtm.SetRegistry(snailMetaKey, rt.TableValue(snailMeta))
|
|
|
|
exports := map[string]util.LuaExport{
|
|
"new": util.LuaExport{snew, 0, false},
|
|
}
|
|
|
|
mod := rt.NewTable()
|
|
util.SetExports(rtm, mod, exports)
|
|
|
|
return rt.TableValue(mod), nil
|
|
}
|
|
|
|
func snew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
s := New(t.Runtime)
|
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snailUserData(s))), nil
|
|
}
|
|
|
|
func srun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
if err := c.CheckNArgs(2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s, err := snailArg(c, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd, err := c.StringArg(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
streams := &util.Streams{}
|
|
thirdArg := c.Arg(2)
|
|
switch thirdArg.Type() {
|
|
case rt.TableType:
|
|
args := thirdArg.AsTable()
|
|
|
|
if luastreams, ok := args.Get(rt.StringValue("sinks")).TryTable(); ok {
|
|
handleStream(luastreams.Get(rt.StringValue("out")), streams, false, false)
|
|
handleStream(luastreams.Get(rt.StringValue("err")), streams, true, false)
|
|
handleStream(luastreams.Get(rt.StringValue("input")), streams, false, true)
|
|
}
|
|
case rt.NilType: // noop
|
|
default:
|
|
return nil, errors.New("expected 3rd arg to either be a table or a boolean")
|
|
}
|
|
|
|
var newline bool
|
|
var cont bool
|
|
var luaErr rt.Value = rt.NilValue
|
|
exitCode := 0
|
|
bg, _, _, err := s.Run(cmd, streams)
|
|
if err != nil {
|
|
if syntax.IsIncomplete(err) {
|
|
/*
|
|
if !interactive {
|
|
return cmdString, 126, false, false, err
|
|
}
|
|
*/
|
|
if strings.Contains(err.Error(), "unclosed here-document") {
|
|
newline = true
|
|
}
|
|
cont = true
|
|
} else {
|
|
if code, ok := interp.IsExitStatus(err); ok {
|
|
exitCode = int(code)
|
|
} else {
|
|
if exErr, ok := util.IsExecError(err); ok {
|
|
exitCode = exErr.Code
|
|
}
|
|
luaErr = rt.StringValue(err.Error())
|
|
}
|
|
}
|
|
}
|
|
runnerRet := rt.NewTable()
|
|
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
|
|
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
|
|
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
|
|
runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
|
|
runnerRet.Set(rt.StringValue("err"), luaErr)
|
|
|
|
runnerRet.Set(rt.StringValue("bg"), rt.BoolValue(bg))
|
|
return c.PushingNext1(t.Runtime, rt.TableValue(runnerRet)), nil
|
|
}
|
|
|
|
func handleStream(v rt.Value, strms *util.Streams, errStream, inStream bool) error {
|
|
if v == rt.NilValue {
|
|
return nil
|
|
}
|
|
|
|
ud, ok := v.TryUserData()
|
|
if !ok {
|
|
return errors.New("expected metatable argument")
|
|
}
|
|
|
|
val := ud.Value()
|
|
var varstrm io.ReadWriter
|
|
if f, ok := val.(*iolib.File); ok {
|
|
varstrm = f.Handle()
|
|
}
|
|
|
|
if f, ok := val.(*sink.Sink); ok {
|
|
varstrm = f.Rw
|
|
}
|
|
|
|
if varstrm == nil {
|
|
return errors.New("expected either a sink or file")
|
|
}
|
|
|
|
if errStream {
|
|
strms.Stderr = varstrm
|
|
} else if inStream {
|
|
strms.Stdin = varstrm
|
|
} else {
|
|
strms.Stdout = varstrm
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func snailArg(c *rt.GoCont, arg int) (*snail, error) {
|
|
s, ok := valueToSnail(c.Arg(arg))
|
|
if !ok {
|
|
return nil, fmt.Errorf("#%d must be a snail", arg + 1)
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func valueToSnail(val rt.Value) (*snail, bool) {
|
|
u, ok := val.TryUserData()
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
|
|
s, ok := u.Value().(*snail)
|
|
return s, ok
|
|
}
|
|
|
|
func snailUserData(s *snail) *rt.UserData {
|
|
snailMeta := s.runtime.Registry(snailMetaKey)
|
|
return rt.NewUserData(s, snailMeta.AsTable())
|
|
}
|