2
2
mirror of https://github.com/Hilbis/Hilbish synced 2025-04-21 21:13:22 +00:00

Compare commits

..

2 Commits

7 changed files with 134 additions and 127 deletions

113
api.go
View File

@ -23,6 +23,7 @@ import (
"syscall"
"time"
"hilbish/sink"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
@ -48,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},
}
@ -133,6 +133,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
pluginModule := moduleLoader(rtm)
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
sinkModule := sink.Loader(l)
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
return rt.TableValue(mod), nil
}
@ -184,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

View File

@ -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 {

2
lua.go
View File

@ -4,7 +4,6 @@ import (
"fmt"
"os"
"hilbish/sink"
"hilbish/util"
"hilbish/golibs/bait"
"hilbish/golibs/commander"
@ -25,7 +24,6 @@ func luaInit() {
MessageHandler: debuglib.Traceback,
})
lib.LoadAll(l)
sink.SetupSinkType(l)
lib.LoadLibs(l, hilbishLoader)
// yes this is stupid, i know

30
nature/hilbish.lua Normal file
View File

@ -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

View File

@ -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'

View File

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

View File

@ -2,6 +2,7 @@ package sink
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
@ -18,14 +19,13 @@ var sinkMetaKey = rt.StringValue("hshsink")
// A sink is a structure that has input and/or output to/from
// a desination.
type Sink struct{
writer *bufio.Writer
reader *bufio.Reader
Rw *bufio.ReadWriter
file *os.File
UserData *rt.UserData
autoFlush bool
}
func SetupSinkType(rtm *rt.Runtime) {
func Loader(rtm *rt.Runtime) *rt.Table {
sinkMeta := rt.NewTable()
sinkMethods := rt.NewTable()
@ -65,9 +65,24 @@ func SetupSinkType(rtm *rt.Runtime) {
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
rtm.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
exports := map[string]util.LuaExport{
"new": {luaSinkNew, 0, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return mod
}
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
}
// #member
// readAll() -> string
// --- @returns string
@ -84,7 +99,7 @@ func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
lines := []string{}
for {
line, err := s.reader.ReadString('\n')
line, err := s.Rw.ReadString('\n')
if err != nil {
if err == io.EOF {
break
@ -113,7 +128,7 @@ func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
str, _ := s.reader.ReadString('\n')
str, _ := s.Rw.ReadString('\n')
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
}
@ -135,9 +150,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
s.writer.Write([]byte(data))
s.Rw.Write([]byte(data))
if s.autoFlush {
s.writer.Flush()
s.Rw.Flush()
}
return c.Next(), nil
@ -160,9 +175,9 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
s.writer.Write([]byte(data + "\n"))
s.Rw.Write([]byte(data + "\n"))
if s.autoFlush {
s.writer.Flush()
s.Rw.Flush()
}
return c.Next(), nil
@ -181,7 +196,7 @@ func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
s.writer.Flush()
s.Rw.Flush()
return c.Next(), nil
}
@ -212,9 +227,22 @@ func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func NewSink(rtm *rt.Runtime, Rw io.ReadWriter) *Sink {
s := &Sink{
Rw: bufio.NewReadWriter(bufio.NewReader(Rw), bufio.NewWriter(Rw)),
}
s.UserData = sinkUserData(rtm, s)
if f, ok := Rw.(*os.File); ok {
s.file = f
}
return s
}
func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink {
s := &Sink{
reader: bufio.NewReader(r),
Rw: bufio.NewReadWriter(bufio.NewReader(r), nil),
}
s.UserData = sinkUserData(rtm, s)
@ -227,7 +255,7 @@ func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink {
func NewSinkOutput(rtm *rt.Runtime, w io.Writer) *Sink {
s := &Sink{
writer: bufio.NewWriter(w),
Rw: bufio.NewReadWriter(nil, bufio.NewWriter(w)),
autoFlush: true,
}
s.UserData = sinkUserData(rtm, s)