From c690691ede20e90c73050405b214310a08887e75 Mon Sep 17 00:00:00 2001 From: sammyette Date: Tue, 17 Jan 2023 23:03:23 -0400 Subject: [PATCH] feat: add sink for commanders to write output/read input to write output, you would usually just use the print builtin since commanders are just lua custom commands but this does not consider the fact of pipes or other shell operators being used to redirect or whatever. this adds readable/writable "sinks" which is a type for input or output and is currently only used for commanders but can be used for other hilbish things in the future --- exec.go | 13 +++++- lua.go | 1 + nature/commands/cat.lua | 4 +- sink.go | 101 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 sink.go diff --git a/exec.go b/exec.go index e18cdd6..726a986 100644 --- a/exec.go +++ b/exec.go @@ -323,8 +323,18 @@ func execHandle(bg bool) interp.ExecHandlerFunc { luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str)) } + hc := interp.HandlerCtx(ctx) if commands[args[0]] != nil { - luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs)) + stdin := newSinkInput(hc.Stdin) + stdout := newSinkOutput(hc.Stdout) + stderr := newSinkOutput(hc.Stderr) + + sinks := rt.NewTable() + sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.ud)) + sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud)) + sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud)) + + luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs), rt.TableValue(sinks)) if err != nil { fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error()) return interp.NewExitStatus(1) @@ -364,7 +374,6 @@ func execHandle(bg bool) interp.ExecHandlerFunc { killTimeout := 2 * time.Second // from here is basically copy-paste of the default exec handler from // sh/interp but with our job handling - hc := interp.HandlerCtx(ctx) path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0]) if err != nil { fmt.Fprintln(hc.Stderr, err) diff --git a/lua.go b/lua.go index 79eb1f7..0a7c115 100644 --- a/lua.go +++ b/lua.go @@ -23,6 +23,7 @@ func luaInit() { MessageHandler: debuglib.Traceback, }) lib.LoadAll(l) + setupSinkType(l) lib.LoadLibs(l, hilbishLoader) // yes this is stupid, i know diff --git a/nature/commands/cat.lua b/nature/commands/cat.lua index 132db5f..52e5cda 100644 --- a/nature/commands/cat.lua +++ b/nature/commands/cat.lua @@ -1,7 +1,7 @@ local commander = require 'commander' local fs = require 'fs' -commander.register('cat', function(args) +commander.register('cat', function(args, sinks) local exit = 0 if #args == 0 then @@ -17,7 +17,7 @@ usage: cat [file]...]] goto continue end - io.write(f:read '*a') + sinks.out:write(f:read '*a') ::continue:: end io.flush() diff --git a/sink.go b/sink.go new file mode 100644 index 0000000..619c3f8 --- /dev/null +++ b/sink.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + "io" + + "hilbish/util" + + rt "github.com/arnodel/golua/runtime" +) + +var sinkMetaKey = rt.StringValue("hshsink") + +// a sink is a structure that has input and/or output +// it is like a lua file when used in popen, but specific to hilbish +type sink struct{ + writer io.Writer + reader io.Reader + ud *rt.UserData +} + +func setupSinkType(rtm *rt.Runtime) { + sinkMeta := rt.NewTable() + + sinkMethods := rt.NewTable() + sinkFuncs := map[string]util.LuaExport{ + "write": {luaSinkWrite, 2, false}, + } + util.SetExports(l, sinkMethods, sinkFuncs) + + sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + arg := c.Arg(1) + val := sinkMethods.Get(arg) + + return c.PushingNext1(t.Runtime, val), nil + } + + sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false))) + l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta)) +} + +func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.CheckNArgs(2); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + data, err := c.StringArg(1) + if err != nil { + return nil, err + } + + s.writer.Write([]byte(data)) + + return c.Next(), nil +} + +func newSinkInput(r io.Reader) *sink { + s := &sink{ + reader: r, + } + s.ud = sinkUserData(s) + + return s +} + +func newSinkOutput(w io.Writer) *sink { + s := &sink{ + writer: w, + } + s.ud = sinkUserData(s) + + return s +} + +func sinkArg(c *rt.GoCont, arg int) (*sink, error) { + s, ok := valueToSink(c.Arg(arg)) + if !ok { + return nil, fmt.Errorf("#%d must be a sink", arg + 1) + } + + return s, nil +} + +func valueToSink(val rt.Value) (*sink, bool) { + u, ok := val.TryUserData() + if !ok { + return nil, false + } + + s, ok := u.Value().(*sink) + return s, ok +} + +func sinkUserData(s *sink) *rt.UserData { + sinkMeta := l.Registry(sinkMetaKey) + return rt.NewUserData(s, sinkMeta.AsTable()) +}