From 15e3c1a74b4e0c164b3ee88cf1bd51982ca39120 Mon Sep 17 00:00:00 2001 From: sammyette Date: Sun, 29 Dec 2024 20:32:21 -0400 Subject: [PATCH] feat: reimplement hilbish.run in lua --- api.go | 109 -------------------------------------------- golibs/snail/lua.go | 59 +++++++++++++++++++++++- nature/hilbish.lua | 30 ++++++++++++ nature/init.lua | 2 + nature/runner.lua | 3 +- sink/sink.go | 26 +++++------ 6 files changed, 103 insertions(+), 126 deletions(-) create mode 100644 nature/hilbish.lua diff --git a/api.go b/api.go index eb1a49b..8c14936 100644 --- a/api.go +++ b/api.go @@ -49,7 +49,6 @@ var exports = map[string]util.LuaExport{ "inputMode": {hlinputMode, 1, false}, "interval": {hlinterval, 2, false}, "read": {hlread, 1, false}, - //"run": {hlrun, 1, true}, "timeout": {hltimeout, 2, false}, "which": {hlwhich, 1, false}, } @@ -188,114 +187,6 @@ func handleStream(v rt.Value, strms *streams, errStream bool) error { } */ -// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string) -// Runs `cmd` in Hilbish's shell script interpreter. -// The `streams` parameter specifies the output and input streams the command should use. -// For example, to write command output to a sink. -// As a table, the caller can directly specify the standard output, error, and input -// streams of the command with the table keys `out`, `err`, and `input` respectively. -// As a boolean, it specifies whether the command should use standard output or return its output streams. -// #param cmd string -// #param streams table|boolean -// #returns number, string, string -// #example -/* -// This code is the same as `ls -l | wc -l` -local fs = require 'fs' -local pr, pw = fs.pipe() -hilbish.run('ls -l', { - stdout = pw, - stderr = pw, -}) - -pw:close() - -hilbish.run('wc -l', { - stdin = pr -}) -*/ -// #example -/* -func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { - // TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN. - if err := c.Check1Arg(); err != nil { - return nil, err - } - cmd, err := c.StringArg(0) - if err != nil { - return nil, err - } - - strms := &streams{} - var terminalOut bool - if len(c.Etc()) != 0 { - tout := c.Etc()[0] - - var ok bool - terminalOut, ok = tout.TryBool() - if !ok { - luastreams, ok := tout.TryTable() - if !ok { - return nil, errors.New("bad argument to run (expected boolean or table, got " + tout.TypeName() + ")") - } - - handleStream(luastreams.Get(rt.StringValue("out")), strms, false) - handleStream(luastreams.Get(rt.StringValue("err")), strms, true) - - stdinstrm := luastreams.Get(rt.StringValue("input")) - if !stdinstrm.IsNil() { - ud, ok := stdinstrm.TryUserData() - if !ok { - return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file, got " + stdinstrm.TypeName() + ")") - } - - val := ud.Value() - var varstrm io.Reader - if f, ok := val.(*iolib.File); ok { - varstrm = f.Handle() - } - - if f, ok := val.(*sink); ok { - varstrm = f.reader - } - - if varstrm == nil { - return nil, errors.New("bad type as run stdin stream (expected userdata as either sink or file)") - } - - strms.stdin = varstrm - } - } else { - if !terminalOut { - strms = &streams{ - stdout: new(bytes.Buffer), - stderr: new(bytes.Buffer), - } - } - } - } - - var exitcode uint8 - stdout, stderr, err := execCommand(cmd, strms) - - if code, ok := interp.IsExitStatus(err); ok { - exitcode = code - } else if err != nil { - exitcode = 1 - } - - var stdoutStr, stderrStr string - if stdoutBuf, ok := stdout.(*bytes.Buffer); ok { - stdoutStr = stdoutBuf.String() - } - if stderrBuf, ok := stderr.(*bytes.Buffer); ok { - stderrStr = stderrBuf.String() - } - - return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil -} -*/ - // cwd() -> string // Returns the current directory of the shell. // #returns string diff --git a/golibs/snail/lua.go b/golibs/snail/lua.go index 61ca254..a8abf0a 100644 --- a/golibs/snail/lua.go +++ b/golibs/snail/lua.go @@ -1,13 +1,17 @@ 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" ) @@ -22,7 +26,7 @@ func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { snailMeta := rt.NewTable() snailMethods := rt.NewTable() snailFuncs := map[string]util.LuaExport{ - "run": {srun, 2, false}, + "run": {srun, 3, false}, } util.SetExports(rtm, snailMethods, snailFuncs) @@ -65,11 +69,27 @@ func srun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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, nil) + bg, _, _, err := s.Run(cmd, streams) if err != nil { if syntax.IsIncomplete(err) { /* @@ -103,6 +123,41 @@ func srun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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 { diff --git a/nature/hilbish.lua b/nature/hilbish.lua new file mode 100644 index 0000000..f37bab9 --- /dev/null +++ b/nature/hilbish.lua @@ -0,0 +1,30 @@ +local hilbish = require 'hilbish' +local snail = require 'snail' + +hilbish.snail = snail.new() + +function hilbish.run(cmd, streams) + local sinks = {} + + if type(streams) == 'boolean' then + if not streams then + sinks = { + out = hilbish.sink.new(), + err = hilbish.sink.new(), + input = io.stdin + } + end + elseif type(streams) == 'table' then + sinks = streams + end + + local out = hilbish.snail:run(cmd, {sinks = sinks}) + local returns = {out} + + if type(streams) == 'boolean' and not streams then + table.insert(returns, sinks.out:readAll()) + table.insert(returns, sinks.err:readAll()) + end + + return table.unpack(returns) +end diff --git a/nature/init.lua b/nature/init.lua index a0579d7..4c47bfe 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -18,6 +18,8 @@ table.insert(package.searchers, function(module) return function() return hilbish.module.load(path) end, path end) +require 'nature.hilbish' + require 'nature.commands' require 'nature.completions' require 'nature.opts' diff --git a/nature/runner.lua b/nature/runner.lua index 9ece224..6bb0b22 100644 --- a/nature/runner.lua +++ b/nature/runner.lua @@ -83,9 +83,8 @@ function hilbish.runner.getCurrent() return currentRunner end -local snaili = snail.new() function hilbish.runner.sh(input) - return snaili:run(input) + return hilbish.snail:run(input) end hilbish.runner.add('hybrid', function(input) diff --git a/sink/sink.go b/sink/sink.go index 4899d89..2b17373 100644 --- a/sink/sink.go +++ b/sink/sink.go @@ -19,7 +19,7 @@ var sinkMetaKey = rt.StringValue("hshsink") // A sink is a structure that has input and/or output to/from // a desination. type Sink struct{ - rw *bufio.ReadWriter + Rw *bufio.ReadWriter file *os.File UserData *rt.UserData autoFlush bool @@ -99,7 +99,7 @@ func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { lines := []string{} for { - line, err := s.rw.ReadString('\n') + line, err := s.Rw.ReadString('\n') if err != nil { if err == io.EOF { break @@ -128,7 +128,7 @@ func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - str, _ := s.rw.ReadString('\n') + str, _ := s.Rw.ReadString('\n') return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil } @@ -150,9 +150,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - s.rw.Write([]byte(data)) + s.Rw.Write([]byte(data)) if s.autoFlush { - s.rw.Flush() + s.Rw.Flush() } return c.Next(), nil @@ -175,9 +175,9 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - s.rw.Write([]byte(data + "\n")) + s.Rw.Write([]byte(data + "\n")) if s.autoFlush { - s.rw.Flush() + s.Rw.Flush() } return c.Next(), nil @@ -196,7 +196,7 @@ func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return nil, err } - s.rw.Flush() + s.Rw.Flush() return c.Next(), nil } @@ -227,13 +227,13 @@ func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { return c.Next(), nil } -func NewSink(rtm *rt.Runtime, rw io.ReadWriter) *Sink { +func NewSink(rtm *rt.Runtime, Rw io.ReadWriter) *Sink { s := &Sink{ - rw: bufio.NewReadWriter(bufio.NewReader(rw), bufio.NewWriter(rw)), + Rw: bufio.NewReadWriter(bufio.NewReader(Rw), bufio.NewWriter(Rw)), } s.UserData = sinkUserData(rtm, s) - if f, ok := rw.(*os.File); ok { + if f, ok := Rw.(*os.File); ok { s.file = f } @@ -242,7 +242,7 @@ func NewSink(rtm *rt.Runtime, rw io.ReadWriter) *Sink { func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink { s := &Sink{ - rw: bufio.NewReadWriter(bufio.NewReader(r), nil), + Rw: bufio.NewReadWriter(bufio.NewReader(r), nil), } s.UserData = sinkUserData(rtm, s) @@ -255,7 +255,7 @@ func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink { func NewSinkOutput(rtm *rt.Runtime, w io.Writer) *Sink { s := &Sink{ - rw: bufio.NewReadWriter(nil, bufio.NewWriter(w)), + Rw: bufio.NewReadWriter(nil, bufio.NewWriter(w)), autoFlush: true, } s.UserData = sinkUserData(rtm, s)