2024-12-29 05:12:06 +00:00
|
|
|
package sink
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
import (
|
2023-03-25 21:42:25 +00:00
|
|
|
"bufio"
|
2024-12-29 23:21:54 +00:00
|
|
|
"bytes"
|
2023-01-20 23:07:42 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2023-03-25 21:42:25 +00:00
|
|
|
"os"
|
2023-10-25 04:41:53 +00:00
|
|
|
"strings"
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
"hilbish/util"
|
|
|
|
|
|
|
|
rt "github.com/arnodel/golua/runtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
var sinkMetaKey = rt.StringValue("hshsink")
|
|
|
|
|
2023-02-07 19:39:22 +00:00
|
|
|
// #type
|
|
|
|
// A sink is a structure that has input and/or output to/from
|
|
|
|
// a desination.
|
2024-12-29 05:12:06 +00:00
|
|
|
type Sink struct{
|
2024-12-30 00:32:21 +00:00
|
|
|
Rw *bufio.ReadWriter
|
2023-03-25 21:42:25 +00:00
|
|
|
file *os.File
|
2024-12-29 05:12:06 +00:00
|
|
|
UserData *rt.UserData
|
2023-03-25 21:42:25 +00:00
|
|
|
autoFlush bool
|
2023-01-20 23:07:42 +00:00
|
|
|
}
|
|
|
|
|
2024-12-29 23:21:54 +00:00
|
|
|
func Loader(rtm *rt.Runtime) *rt.Table {
|
2023-01-20 23:07:42 +00:00
|
|
|
sinkMeta := rt.NewTable()
|
|
|
|
|
|
|
|
sinkMethods := rt.NewTable()
|
|
|
|
sinkFuncs := map[string]util.LuaExport{
|
2023-03-25 21:42:25 +00:00
|
|
|
"flush": {luaSinkFlush, 1, false},
|
|
|
|
"read": {luaSinkRead, 1, false},
|
2023-10-25 04:41:53 +00:00
|
|
|
"readAll": {luaSinkReadAll, 1, false},
|
2023-03-25 21:42:25 +00:00
|
|
|
"autoFlush": {luaSinkAutoFlush, 2, false},
|
2023-01-20 23:07:42 +00:00
|
|
|
"write": {luaSinkWrite, 2, false},
|
|
|
|
"writeln": {luaSinkWriteln, 2, false},
|
|
|
|
}
|
2024-12-29 05:12:06 +00:00
|
|
|
util.SetExports(rtm, sinkMethods, sinkFuncs)
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
2023-03-25 21:42:25 +00:00
|
|
|
s, _ := sinkArg(c, 0)
|
|
|
|
|
2023-01-20 23:07:42 +00:00
|
|
|
arg := c.Arg(1)
|
|
|
|
val := sinkMethods.Get(arg)
|
|
|
|
|
2023-03-25 21:42:25 +00:00
|
|
|
if val != rt.NilValue {
|
|
|
|
return c.PushingNext1(t.Runtime, val), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
keyStr, _ := arg.TryString()
|
|
|
|
|
|
|
|
switch keyStr {
|
|
|
|
case "pipe":
|
|
|
|
val = rt.BoolValue(false)
|
|
|
|
if s.file != nil {
|
|
|
|
fileInfo, _ := s.file.Stat();
|
|
|
|
val = rt.BoolValue(fileInfo.Mode() & os.ModeCharDevice == 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-20 23:07:42 +00:00
|
|
|
return c.PushingNext1(t.Runtime, val), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
|
2024-12-29 05:12:06 +00:00
|
|
|
rtm.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
2024-12-29 23:21:54 +00:00
|
|
|
|
|
|
|
exports := map[string]util.LuaExport{
|
|
|
|
"new": {luaSinkNew, 0, false},
|
|
|
|
}
|
|
|
|
|
|
|
|
mod := rt.NewTable()
|
|
|
|
util.SetExports(rtm, mod, exports)
|
|
|
|
|
|
|
|
return mod
|
2023-01-20 23:07:42 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 04:41:53 +00:00
|
|
|
|
2024-12-29 23:21:54 +00:00
|
|
|
func luaSinkNew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
snk := NewSink(t.Runtime, new(bytes.Buffer))
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snk.UserData)), nil
|
|
|
|
}
|
|
|
|
|
2023-10-25 04:41:53 +00:00
|
|
|
// #member
|
|
|
|
// readAll() -> string
|
|
|
|
// --- @returns string
|
|
|
|
// Reads all input from the sink.
|
|
|
|
func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := sinkArg(c, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lines := []string{}
|
|
|
|
for {
|
2024-12-30 00:32:21 +00:00
|
|
|
line, err := s.Rw.ReadString('\n')
|
2023-10-25 04:41:53 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(strings.Join(lines, ""))), nil
|
|
|
|
}
|
|
|
|
|
2023-03-25 21:42:25 +00:00
|
|
|
// #member
|
|
|
|
// read() -> string
|
|
|
|
// --- @returns string
|
2023-10-25 04:41:53 +00:00
|
|
|
// Reads a liine of input from the sink.
|
2023-03-25 21:42:25 +00:00
|
|
|
func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := sinkArg(c, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
str, _ := s.Rw.ReadString('\n')
|
2023-03-25 21:42:25 +00:00
|
|
|
|
|
|
|
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:39:22 +00:00
|
|
|
// #member
|
|
|
|
// write(str)
|
|
|
|
// Writes data to a sink.
|
2023-01-20 23:07:42 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
s.Rw.Write([]byte(data))
|
2023-03-25 21:42:25 +00:00
|
|
|
if s.autoFlush {
|
2024-12-30 00:32:21 +00:00
|
|
|
s.Rw.Flush()
|
2023-03-25 21:42:25 +00:00
|
|
|
}
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
return c.Next(), nil
|
|
|
|
}
|
|
|
|
|
2023-02-07 19:39:22 +00:00
|
|
|
// #member
|
|
|
|
// writeln(str)
|
|
|
|
// Writes data to a sink with a newline at the end.
|
2023-01-20 23:07:42 +00:00
|
|
|
func luaSinkWriteln(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
|
|
|
|
}
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
s.Rw.Write([]byte(data + "\n"))
|
2023-03-25 21:42:25 +00:00
|
|
|
if s.autoFlush {
|
2024-12-30 00:32:21 +00:00
|
|
|
s.Rw.Flush()
|
2023-03-25 21:42:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.Next(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// #member
|
|
|
|
// flush()
|
|
|
|
// Flush writes all buffered input to the sink.
|
|
|
|
func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
if err := c.Check1Arg(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := sinkArg(c, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
s.Rw.Flush()
|
2023-03-25 21:42:25 +00:00
|
|
|
|
|
|
|
return c.Next(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// #member
|
|
|
|
// autoFlush(auto)
|
|
|
|
// Sets/toggles the option of automatically flushing output.
|
|
|
|
// A call with no argument will toggle the value.
|
|
|
|
// --- @param auto boolean|nil
|
|
|
|
func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
|
|
s, err := sinkArg(c, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
v := c.Arg(1)
|
|
|
|
if v.Type() != rt.BoolType && v.Type() != rt.NilType {
|
|
|
|
return nil, fmt.Errorf("#1 must be a boolean")
|
|
|
|
}
|
|
|
|
|
|
|
|
value := !s.autoFlush
|
|
|
|
if v.Type() == rt.BoolType {
|
|
|
|
value = v.AsBool()
|
|
|
|
}
|
|
|
|
|
|
|
|
s.autoFlush = value
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
return c.Next(), nil
|
|
|
|
}
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
func NewSink(rtm *rt.Runtime, Rw io.ReadWriter) *Sink {
|
2024-12-29 23:21:54 +00:00
|
|
|
s := &Sink{
|
2024-12-30 00:32:21 +00:00
|
|
|
Rw: bufio.NewReadWriter(bufio.NewReader(Rw), bufio.NewWriter(Rw)),
|
2024-12-29 23:21:54 +00:00
|
|
|
}
|
|
|
|
s.UserData = sinkUserData(rtm, s)
|
|
|
|
|
2024-12-30 00:32:21 +00:00
|
|
|
if f, ok := Rw.(*os.File); ok {
|
2024-12-29 23:21:54 +00:00
|
|
|
s.file = f
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink {
|
|
|
|
s := &Sink{
|
2024-12-30 00:32:21 +00:00
|
|
|
Rw: bufio.NewReadWriter(bufio.NewReader(r), nil),
|
2023-01-20 23:07:42 +00:00
|
|
|
}
|
2024-12-29 05:12:06 +00:00
|
|
|
s.UserData = sinkUserData(rtm, s)
|
2023-01-20 23:07:42 +00:00
|
|
|
|
2023-03-25 21:42:25 +00:00
|
|
|
if f, ok := r.(*os.File); ok {
|
|
|
|
s.file = f
|
|
|
|
}
|
|
|
|
|
2023-01-20 23:07:42 +00:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
func NewSinkOutput(rtm *rt.Runtime, w io.Writer) *Sink {
|
|
|
|
s := &Sink{
|
2024-12-30 00:32:21 +00:00
|
|
|
Rw: bufio.NewReadWriter(nil, bufio.NewWriter(w)),
|
2023-03-25 21:42:25 +00:00
|
|
|
autoFlush: true,
|
2023-01-20 23:07:42 +00:00
|
|
|
}
|
2024-12-29 05:12:06 +00:00
|
|
|
s.UserData = sinkUserData(rtm, s)
|
2023-01-20 23:07:42 +00:00
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
func sinkArg(c *rt.GoCont, arg int) (*Sink, error) {
|
2023-01-20 23:07:42 +00:00
|
|
|
s, ok := valueToSink(c.Arg(arg))
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("#%d must be a sink", arg + 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
func valueToSink(val rt.Value) (*Sink, bool) {
|
2023-01-20 23:07:42 +00:00
|
|
|
u, ok := val.TryUserData()
|
|
|
|
if !ok {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
s, ok := u.Value().(*Sink)
|
2023-01-20 23:07:42 +00:00
|
|
|
return s, ok
|
|
|
|
}
|
|
|
|
|
2024-12-29 05:12:06 +00:00
|
|
|
func sinkUserData(rtm *rt.Runtime, s *Sink) *rt.UserData {
|
|
|
|
sinkMeta := rtm.Registry(sinkMetaKey)
|
2023-01-20 23:07:42 +00:00
|
|
|
return rt.NewUserData(s, sinkMeta.AsTable())
|
|
|
|
}
|