mirror of
https://github.com/Hilbis/Hilbish
synced 2025-07-01 16:52:03 +00:00
224 lines
5.0 KiB
Go
224 lines
5.0 KiB
Go
// shell script interpreter library
|
|
/*
|
|
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
|
It's not very useful other than running shell scripts, which can be done with other
|
|
Hilbish functions.
|
|
*/
|
|
package snail
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"hilbish/moonlight"
|
|
"hilbish/util"
|
|
|
|
"github.com/arnodel/golua/lib/iolib"
|
|
rt "github.com/arnodel/golua/runtime"
|
|
"mvdan.cc/sh/v3/interp"
|
|
"mvdan.cc/sh/v3/syntax"
|
|
)
|
|
|
|
var snailMetaKey = rt.StringValue("hshsnail")
|
|
|
|
func Loader(mlr *moonlight.Runtime) moonlight.Value {
|
|
snailMeta := moonlight.NewTable()
|
|
snailMethods := moonlight.NewTable()
|
|
snailFuncs := map[string]moonlight.Export{
|
|
"run": {snailrun, 3, false},
|
|
"dir": {snaildir, 2, false},
|
|
}
|
|
mlr.SetExports(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)))
|
|
mlr.SetRegistry(snailMetaKey, moonlight.TableValue(snailMeta))
|
|
|
|
exports := map[string]moonlight.Export{
|
|
"new": {snailnew, 0, false},
|
|
}
|
|
|
|
mod := moonlight.NewTable()
|
|
mlr.SetExports(mod, exports)
|
|
|
|
return moonlight.TableValue(mod)
|
|
}
|
|
|
|
// new() -> @Snail
|
|
// Creates a new Snail instance.
|
|
func snailnew(mlr *moonlight.Runtime) error {
|
|
s := New(mlr)
|
|
|
|
mlr.PushNext1(moonlight.UserDataValue(snailUserData(s)))
|
|
return nil
|
|
}
|
|
|
|
// #member
|
|
// run(command, streams)
|
|
// Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
|
// #param command string
|
|
// #param streams table
|
|
func snailrun(mlr *moonlight.Runtime) error {
|
|
if err := mlr.CheckNArgs(2); err != nil {
|
|
return err
|
|
}
|
|
|
|
s, err := snailArg(mlr, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err := mlr.StringArg(1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
streams := &util.Streams{}
|
|
thirdArg := mlr.Arg(2)
|
|
switch thirdArg.Type() {
|
|
case moonlight.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 moonlight.NilType: // noop
|
|
default:
|
|
return errors.New("expected 3rd arg to be a table")
|
|
}
|
|
|
|
var newline bool
|
|
var cont bool
|
|
var luaErr moonlight.Value = moonlight.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 = moonlight.StringValue(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
runnerRet := moonlight.NewTable()
|
|
runnerRet.SetField("input", moonlight.StringValue(cmd))
|
|
runnerRet.SetField("exitCode", moonlight.IntValue(int64(exitCode)))
|
|
runnerRet.SetField("continue", moonlight.BoolValue(cont))
|
|
runnerRet.SetField("newline", moonlight.BoolValue(newline))
|
|
runnerRet.SetField("err", luaErr)
|
|
runnerRet.SetField("bg", moonlight.BoolValue(bg))
|
|
|
|
mlr.PushNext1(moonlight.TableValue(runnerRet))
|
|
return nil
|
|
}
|
|
|
|
// #member
|
|
// dir(path)
|
|
// Changes the directory of the snail instance.
|
|
// The interpreter keeps its set directory even when the Hilbish process changes
|
|
// directory, so this should be called on the `hilbish.cd` hook.
|
|
// #param path string Has to be an absolute path.
|
|
func snaildir(mlr *moonlight.Runtime) error {
|
|
if err := mlr.CheckNArgs(2); err != nil {
|
|
return err
|
|
}
|
|
|
|
s, err := snailArg(mlr, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dir, err := mlr.StringArg(1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
interp.Dir(dir)(s.runner)
|
|
return 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.(*util.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(mlr *moonlight.Runtime, arg int) (*Snail, error) {
|
|
s, ok := valueToSnail(mlr.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) *moonlight.UserData {
|
|
snailMeta := s.runtime.Registry(snailMetaKey)
|
|
return moonlight.NewUserData(s, moonlight.ToTable(s.runtime, snailMeta))
|
|
}
|