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
commander-stdout
sammyette 2023-01-17 23:03:23 -04:00
parent 97188e73a5
commit c690691ede
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
4 changed files with 115 additions and 4 deletions

13
exec.go
View File

@ -323,8 +323,18 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str)) luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
} }
hc := interp.HandlerCtx(ctx)
if commands[args[0]] != nil { 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 { if err != nil {
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error()) fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
return interp.NewExitStatus(1) return interp.NewExitStatus(1)
@ -364,7 +374,6 @@ func execHandle(bg bool) interp.ExecHandlerFunc {
killTimeout := 2 * time.Second killTimeout := 2 * time.Second
// from here is basically copy-paste of the default exec handler from // from here is basically copy-paste of the default exec handler from
// sh/interp but with our job handling // sh/interp but with our job handling
hc := interp.HandlerCtx(ctx)
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0]) path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
if err != nil { if err != nil {
fmt.Fprintln(hc.Stderr, err) fmt.Fprintln(hc.Stderr, err)

1
lua.go
View File

@ -23,6 +23,7 @@ func luaInit() {
MessageHandler: debuglib.Traceback, MessageHandler: debuglib.Traceback,
}) })
lib.LoadAll(l) lib.LoadAll(l)
setupSinkType(l)
lib.LoadLibs(l, hilbishLoader) lib.LoadLibs(l, hilbishLoader)
// yes this is stupid, i know // yes this is stupid, i know

View File

@ -1,7 +1,7 @@
local commander = require 'commander' local commander = require 'commander'
local fs = require 'fs' local fs = require 'fs'
commander.register('cat', function(args) commander.register('cat', function(args, sinks)
local exit = 0 local exit = 0
if #args == 0 then if #args == 0 then
@ -17,7 +17,7 @@ usage: cat [file]...]]
goto continue goto continue
end end
io.write(f:read '*a') sinks.out:write(f:read '*a')
::continue:: ::continue::
end end
io.flush() io.flush()

101
sink.go 100644
View File

@ -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())
}