mirror of https://github.com/Hilbis/Hilbish
feat: implement snail library
parent
571fcc3e9e
commit
5261819149
16
api.go
16
api.go
|
@ -13,10 +13,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
//"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -28,9 +27,9 @@ import (
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"github.com/arnodel/golua/lib/packagelib"
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"github.com/arnodel/golua/lib/iolib"
|
//"github.com/arnodel/golua/lib/iolib"
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"mvdan.cc/sh/v3/interp"
|
//"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exports = map[string]util.LuaExport{
|
var exports = map[string]util.LuaExport{
|
||||||
|
@ -49,7 +48,7 @@ var exports = map[string]util.LuaExport{
|
||||||
"inputMode": {hlinputMode, 1, false},
|
"inputMode": {hlinputMode, 1, false},
|
||||||
"interval": {hlinterval, 2, false},
|
"interval": {hlinterval, 2, false},
|
||||||
"read": {hlread, 1, false},
|
"read": {hlread, 1, false},
|
||||||
"run": {hlrun, 1, true},
|
//"run": {hlrun, 1, true},
|
||||||
"timeout": {hltimeout, 2, false},
|
"timeout": {hltimeout, 2, false},
|
||||||
"which": {hlwhich, 1, false},
|
"which": {hlwhich, 1, false},
|
||||||
}
|
}
|
||||||
|
@ -154,6 +153,7 @@ func unsetVimMode() {
|
||||||
util.SetField(l, hshMod, "vimMode", rt.NilValue)
|
util.SetField(l, hshMod, "vimMode", rt.NilValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
||||||
ud, ok := v.TryUserData()
|
ud, ok := v.TryUserData()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -182,6 +182,7 @@ func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
|
// run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
|
||||||
// Runs `cmd` in Hilbish's shell script interpreter.
|
// Runs `cmd` in Hilbish's shell script interpreter.
|
||||||
|
@ -210,6 +211,7 @@ hilbish.run('wc -l', {
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
// #example
|
// #example
|
||||||
|
/*
|
||||||
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
// TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
|
// TODO: ON BREAKING RELEASE, DO NOT ACCEPT `streams` AS A BOOLEAN.
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
@ -288,6 +290,7 @@ func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// cwd() -> string
|
// cwd() -> string
|
||||||
// Returns the current directory of the shell.
|
// Returns the current directory of the shell.
|
||||||
|
@ -743,6 +746,8 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// runnerMode(mode)
|
// runnerMode(mode)
|
||||||
|
// **NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
// Use `hilbish.runner.setCurrent` instead.
|
||||||
// Sets the execution/runner mode for interactive Hilbish.
|
// Sets the execution/runner mode for interactive Hilbish.
|
||||||
// This determines whether Hilbish wll try to run input as Lua
|
// This determines whether Hilbish wll try to run input as Lua
|
||||||
// and/or sh or only do one of either.
|
// and/or sh or only do one of either.
|
||||||
|
@ -752,6 +757,7 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
// Read [about runner mode](../features/runner-mode) for more information.
|
// Read [about runner mode](../features/runner-mode) for more information.
|
||||||
// #param mode string|function
|
// #param mode string|function
|
||||||
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
// TODO: Reimplement in Lua
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
if len(fileCompletions) != 0 {
|
if len(fileCompletions) != 0 {
|
||||||
for _, f := range fileCompletions {
|
for _, f := range fileCompletions {
|
||||||
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
||||||
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
if err := util.FindExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
completions = append(completions, f)
|
completions = append(completions, f)
|
||||||
|
@ -115,7 +115,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
// get basename from matches
|
// get basename from matches
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
// check if we have execute permissions for our match
|
// check if we have execute permissions for our match
|
||||||
err := findExecutable(match, true, false)
|
err := util.FindExecutable(match, true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
386
exec.go
386
exec.go
|
@ -1,141 +1,45 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os/exec"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
//herror "hilbish/errors"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"mvdan.cc/sh/v3/shell"
|
|
||||||
//"github.com/yuin/gopher-lua/parse"
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNotExec = errors.New("not executable")
|
var errNotExec = errors.New("not executable")
|
||||||
var errNotFound = errors.New("not found")
|
var errNotFound = errors.New("not found")
|
||||||
var runnerMode rt.Value = rt.StringValue("hybrid")
|
var runnerMode rt.Value = rt.StringValue("hybrid")
|
||||||
|
|
||||||
type streams struct {
|
|
||||||
stdout io.Writer
|
|
||||||
stderr io.Writer
|
|
||||||
stdin io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
type execError struct{
|
|
||||||
typ string
|
|
||||||
cmd string
|
|
||||||
code int
|
|
||||||
colon bool
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e execError) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s", e.cmd, e.typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e execError) sprint() error {
|
|
||||||
sep := " "
|
|
||||||
if e.colon {
|
|
||||||
sep = ": "
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("hilbish: %s%s%s", e.cmd, sep, e.err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func isExecError(err error) (execError, bool) {
|
|
||||||
if exErr, ok := err.(execError); ok {
|
|
||||||
return exErr, true
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(err.Error(), ": ")
|
|
||||||
knownTypes := []string{
|
|
||||||
"not-found",
|
|
||||||
"not-executable",
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fields) > 1 && contains(knownTypes, fields[1]) {
|
|
||||||
var colon bool
|
|
||||||
var e error
|
|
||||||
switch fields[1] {
|
|
||||||
case "not-found":
|
|
||||||
e = errNotFound
|
|
||||||
case "not-executable":
|
|
||||||
colon = true
|
|
||||||
e = errNotExec
|
|
||||||
}
|
|
||||||
|
|
||||||
return execError{
|
|
||||||
cmd: fields[0],
|
|
||||||
typ: fields[1],
|
|
||||||
colon: colon,
|
|
||||||
err: e,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return execError{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInput(input string, priv bool) {
|
func runInput(input string, priv bool) {
|
||||||
running = true
|
running = true
|
||||||
cmdString := aliases.Resolve(input)
|
cmdString := aliases.Resolve(input)
|
||||||
hooks.Emit("command.preexec", input, cmdString)
|
hooks.Emit("command.preexec", input, cmdString)
|
||||||
|
|
||||||
|
currentRunner := runnerMode
|
||||||
|
|
||||||
rerun:
|
rerun:
|
||||||
var exitCode uint8
|
var exitCode uint8
|
||||||
var err error
|
|
||||||
var cont bool
|
var cont bool
|
||||||
var newline bool
|
var newline bool
|
||||||
// save incase it changes while prompting (For some reason)
|
// save incase it changes while prompting (For some reason)
|
||||||
currentRunner := runnerMode
|
input, exitCode, cont, newline, runnerErr, err := runLuaRunner(currentRunner, input)
|
||||||
if currentRunner.Type() == rt.StringType {
|
if err != nil {
|
||||||
switch currentRunner.AsString() {
|
fmt.Fprintln(os.Stderr, err)
|
||||||
case "hybrid":
|
cmdFinish(124, input, priv)
|
||||||
_, _, err = handleLua(input)
|
return
|
||||||
if err == nil {
|
|
||||||
cmdFinish(0, input, priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
input, exitCode, cont, newline, err = handleSh(input)
|
|
||||||
case "hybridRev":
|
|
||||||
_, _, _, _, err = handleSh(input)
|
|
||||||
if err == nil {
|
|
||||||
cmdFinish(0, input, priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
input, exitCode, err = handleLua(input)
|
|
||||||
case "lua":
|
|
||||||
input, exitCode, err = handleLua(input)
|
|
||||||
case "sh":
|
|
||||||
input, exitCode, cont, newline, err = handleSh(input)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// can only be a string or function so
|
|
||||||
var runnerErr error
|
|
||||||
input, exitCode, cont, newline, runnerErr, err = runLuaRunner(currentRunner, input)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
cmdFinish(124, input, priv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// yep, we only use `err` to check for lua eval error
|
|
||||||
// our actual error should only be a runner provided error at this point
|
|
||||||
// command not found type, etc
|
|
||||||
err = runnerErr
|
|
||||||
}
|
}
|
||||||
|
// we only use `err` to check for lua eval error
|
||||||
|
// our actual error should only be a runner provided error at this point
|
||||||
|
// command not found type, etc
|
||||||
|
err = runnerErr
|
||||||
|
|
||||||
if cont {
|
if cont {
|
||||||
input, err = continuePrompt(input, newline)
|
input, err = continuePrompt(input, newline)
|
||||||
|
@ -147,8 +51,8 @@ func runInput(input string, priv bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
if exErr, ok := isExecError(err); ok {
|
if exErr, ok := util.IsExecError(err); ok {
|
||||||
hooks.Emit("command." + exErr.typ, exErr.cmd)
|
hooks.Emit("command." + exErr.Typ, exErr.Cmd)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
@ -239,16 +143,7 @@ func handleLua(input string) (string, uint8, error) {
|
||||||
return cmdString, 125, err
|
return cmdString, 125, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSh(cmdString string) (input string, exitCode uint8, cont bool, newline bool, runErr error) {
|
/*
|
||||||
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
|
|
||||||
var err error
|
|
||||||
input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
|
|
||||||
if err != nil {
|
|
||||||
runErr = err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
|
func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
|
||||||
_, _, err := execCommand(cmdString, nil)
|
_, _, err := execCommand(cmdString, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -274,256 +169,7 @@ func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline
|
||||||
|
|
||||||
return cmdString, 0, false, false, nil
|
return cmdString, 0, false, false, nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Run command in sh interpreter
|
|
||||||
func execCommand(cmd string, strms *streams) (io.Writer, io.Writer, error) {
|
|
||||||
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms == nil {
|
|
||||||
strms = &streams{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stdout == nil {
|
|
||||||
strms.stdout = os.Stdout
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stderr == nil {
|
|
||||||
strms.stderr = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
if strms.stdin == nil {
|
|
||||||
strms.stdin = os.Stdin
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.StdIO(strms.stdin, strms.stdout, strms.stderr)(runner)
|
|
||||||
interp.Env(nil)(runner)
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
printer := syntax.NewPrinter()
|
|
||||||
|
|
||||||
var bg bool
|
|
||||||
for _, stmt := range file.Stmts {
|
|
||||||
bg = false
|
|
||||||
if stmt.Background {
|
|
||||||
bg = true
|
|
||||||
printer.Print(buf, stmt.Cmd)
|
|
||||||
|
|
||||||
stmtStr := buf.String()
|
|
||||||
buf.Reset()
|
|
||||||
jobs.add(stmtStr, []string{}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
interp.ExecHandler(execHandle(bg))(runner)
|
|
||||||
err = runner.Run(context.TODO(), stmt)
|
|
||||||
if err != nil {
|
|
||||||
return strms.stdout, strms.stderr, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strms.stdout, strms.stderr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func execHandle(bg bool) interp.ExecHandlerFunc {
|
|
||||||
return func(ctx context.Context, args []string) error {
|
|
||||||
_, argstring := splitInput(strings.Join(args, " "))
|
|
||||||
// i dont really like this but it works
|
|
||||||
if aliases.All()[args[0]] != "" {
|
|
||||||
for i, arg := range args {
|
|
||||||
if strings.Contains(arg, " ") {
|
|
||||||
args[i] = fmt.Sprintf("\"%s\"", arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, argstring = splitInput(strings.Join(args, " "))
|
|
||||||
|
|
||||||
// If alias was found, use command alias
|
|
||||||
argstring = aliases.Resolve(argstring)
|
|
||||||
var err error
|
|
||||||
args, err = shell.Fields(argstring, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If command is defined in Lua then run it
|
|
||||||
luacmdArgs := rt.NewTable()
|
|
||||||
for i, str := range args[1:] {
|
|
||||||
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := interp.HandlerCtx(ctx)
|
|
||||||
if cmd := cmds.Commands[args[0]]; cmd != nil {
|
|
||||||
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("input"), rt.UserDataValue(stdin.ud))
|
|
||||||
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.ud))
|
|
||||||
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.ud))
|
|
||||||
|
|
||||||
t := rt.NewThread(l)
|
|
||||||
sig := make(chan os.Signal)
|
|
||||||
exit := make(chan bool)
|
|
||||||
|
|
||||||
luaexitcode := rt.IntValue(63)
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
exit <- true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
signal.Notify(sig, os.Interrupt)
|
|
||||||
select {
|
|
||||||
case <-sig:
|
|
||||||
t.KillContext()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
|
||||||
exit <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-exit
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
|
||||||
return interp.NewExitStatus(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var exitcode uint8
|
|
||||||
|
|
||||||
if code, ok := luaexitcode.TryInt(); ok {
|
|
||||||
exitcode = uint8(code)
|
|
||||||
} else if luaexitcode != rt.NilValue {
|
|
||||||
// deregister commander
|
|
||||||
delete(cmds.Commands, args[0])
|
|
||||||
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp.NewExitStatus(exitcode)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := util.LookPath(args[0])
|
|
||||||
if err == errNotExec {
|
|
||||||
return execError{
|
|
||||||
typ: "not-executable",
|
|
||||||
cmd: args[0],
|
|
||||||
code: 126,
|
|
||||||
colon: true,
|
|
||||||
err: errNotExec,
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return execError{
|
|
||||||
typ: "not-found",
|
|
||||||
cmd: args[0],
|
|
||||||
code: 127,
|
|
||||||
err: errNotFound,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
killTimeout := 2 * time.Second
|
|
||||||
// from here is basically copy-paste of the default exec handler from
|
|
||||||
// sh/interp but with our job handling
|
|
||||||
|
|
||||||
env := hc.Env
|
|
||||||
envList := os.Environ()
|
|
||||||
env.Each(func(name string, vr expand.Variable) bool {
|
|
||||||
if vr.Exported && vr.Kind == expand.String {
|
|
||||||
envList = append(envList, name+"="+vr.String())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
cmd := exec.Cmd{
|
|
||||||
Path: path,
|
|
||||||
Args: args,
|
|
||||||
Env: envList,
|
|
||||||
Dir: hc.Dir,
|
|
||||||
Stdin: hc.Stdin,
|
|
||||||
Stdout: hc.Stdout,
|
|
||||||
Stderr: hc.Stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
var j *job
|
|
||||||
if bg {
|
|
||||||
j = jobs.getLatest()
|
|
||||||
j.setHandle(&cmd)
|
|
||||||
err = j.start()
|
|
||||||
} else {
|
|
||||||
err = cmd.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
if done := ctx.Done(); done != nil {
|
|
||||||
go func() {
|
|
||||||
<-done
|
|
||||||
|
|
||||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
|
||||||
cmd.Process.Signal(os.Kill)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: don't temporarily leak this goroutine
|
|
||||||
// if the program stops itself with the
|
|
||||||
// interrupt.
|
|
||||||
go func() {
|
|
||||||
time.Sleep(killTimeout)
|
|
||||||
cmd.Process.Signal(os.Kill)
|
|
||||||
}()
|
|
||||||
cmd.Process.Signal(os.Interrupt)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
exit := handleExecErr(err)
|
|
||||||
|
|
||||||
if bg {
|
|
||||||
j.exitCode = int(exit)
|
|
||||||
j.finish()
|
|
||||||
}
|
|
||||||
return interp.NewExitStatus(exit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleExecErr(err error) (exit uint8) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
switch x := err.(type) {
|
|
||||||
case *exec.ExitError:
|
|
||||||
// started, but errored - default to 1 if OS
|
|
||||||
// doesn't have exit statuses
|
|
||||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
|
||||||
if status.Signaled() {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = uint8(128 + status.Signal())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = uint8(status.ExitStatus())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exit = 1
|
|
||||||
return
|
|
||||||
case *exec.Error:
|
|
||||||
// did not start
|
|
||||||
//fmt.Fprintf(hc.Stderr, "%v\n", err)
|
|
||||||
exit = 127
|
|
||||||
default: return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitInput(input string) ([]string, string) {
|
func splitInput(input string) ([]string, string) {
|
||||||
// end my suffering
|
// end my suffering
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
var snailMetaKey = rt.StringValue("hshsnail")
|
||||||
|
var Loader = packagelib.Loader{
|
||||||
|
Load: loaderFunc,
|
||||||
|
Name: "fs",
|
||||||
|
}
|
||||||
|
|
||||||
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
snailMeta := rt.NewTable()
|
||||||
|
snailMethods := rt.NewTable()
|
||||||
|
snailFuncs := map[string]util.LuaExport{
|
||||||
|
"run": {srun, 1, false},
|
||||||
|
}
|
||||||
|
util.SetExports(rtm, snailMethods, snailFuncs)
|
||||||
|
|
||||||
|
snailIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
arg := c.Arg(1)
|
||||||
|
val := snailMethods.Get(arg)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, val), nil
|
||||||
|
}
|
||||||
|
snailMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(snailIndex, "__index", 2, false)))
|
||||||
|
rtm.SetRegistry(snailMetaKey, rt.TableValue(snailMeta))
|
||||||
|
|
||||||
|
exports := map[string]util.LuaExport{
|
||||||
|
"new": util.LuaExport{snew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return rt.TableValue(mod), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func snew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
s := New(t.Runtime)
|
||||||
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snailUserData(s))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func srun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := snailArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newline bool
|
||||||
|
var cont bool
|
||||||
|
var luaErr rt.Value = rt.NilValue
|
||||||
|
exitCode := 0
|
||||||
|
bg, _, _, err := s.Run(cmd, nil)
|
||||||
|
if err != nil {
|
||||||
|
if syntax.IsIncomplete(err) {
|
||||||
|
/*
|
||||||
|
if !interactive {
|
||||||
|
return cmdString, 126, false, false, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if strings.Contains(err.Error(), "unclosed here-document") {
|
||||||
|
newline = true
|
||||||
|
}
|
||||||
|
cont = true
|
||||||
|
} else {
|
||||||
|
if code, ok := interp.IsExitStatus(err); ok {
|
||||||
|
exitCode = int(code)
|
||||||
|
} else {
|
||||||
|
luaErr = rt.StringValue(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runnerRet := rt.NewTable()
|
||||||
|
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
|
||||||
|
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
|
||||||
|
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
|
||||||
|
runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
|
||||||
|
runnerRet.Set(rt.StringValue("err"), luaErr)
|
||||||
|
|
||||||
|
runnerRet.Set(rt.StringValue("bg"), rt.BoolValue(bg))
|
||||||
|
return c.PushingNext1(t.Runtime, rt.TableValue(runnerRet)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func snailArg(c *rt.GoCont, arg int) (*snail, error) {
|
||||||
|
s, ok := valueToSnail(c.Arg(arg))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("#%d must be a snail", arg + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToSnail(val rt.Value) (*snail, bool) {
|
||||||
|
u, ok := val.TryUserData()
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := u.Value().(*snail)
|
||||||
|
return s, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func snailUserData(s *snail) *rt.UserData {
|
||||||
|
snailMeta := s.runtime.Registry(snailMetaKey)
|
||||||
|
return rt.NewUserData(s, snailMeta.AsTable())
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
// shell script interpreter library
|
||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hilbish/sink"
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"mvdan.cc/sh/v3/shell"
|
||||||
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type snail struct{
|
||||||
|
runner *interp.Runner
|
||||||
|
runtime *rt.Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(rtm *rt.Runtime) *snail {
|
||||||
|
runner, _ := interp.New()
|
||||||
|
|
||||||
|
return &snail{
|
||||||
|
runner: runner,
|
||||||
|
runtime: rtm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snail) Run(cmd string, strms *util.Streams) (bool, io.Writer, io.Writer, error){
|
||||||
|
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms == nil {
|
||||||
|
strms = &util.Streams{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stdout == nil {
|
||||||
|
strms.Stdout = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stderr == nil {
|
||||||
|
strms.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if strms.Stdin == nil {
|
||||||
|
strms.Stdin = os.Stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.StdIO(strms.Stdin, strms.Stdout, strms.Stderr)(s.runner)
|
||||||
|
interp.Env(nil)(s.runner)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
//printer := syntax.NewPrinter()
|
||||||
|
|
||||||
|
var bg bool
|
||||||
|
for _, stmt := range file.Stmts {
|
||||||
|
bg = false
|
||||||
|
if stmt.Background {
|
||||||
|
bg = true
|
||||||
|
//printer.Print(buf, stmt.Cmd)
|
||||||
|
|
||||||
|
//stmtStr := buf.String()
|
||||||
|
buf.Reset()
|
||||||
|
//jobs.add(stmtStr, []string{}, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.ExecHandler(func(ctx context.Context, args []string) error {
|
||||||
|
_, argstring := splitInput(strings.Join(args, " "))
|
||||||
|
// i dont really like this but it works
|
||||||
|
aliases := make(map[string]string)
|
||||||
|
aliasesLua, _ := util.DoString(s.runtime, "return hilbish.aliases.all()")
|
||||||
|
util.ForEach(aliasesLua.AsTable(), func(k, v rt.Value) {
|
||||||
|
aliases[k.AsString()] = v.AsString()
|
||||||
|
})
|
||||||
|
if aliases[args[0]] != "" {
|
||||||
|
for i, arg := range args {
|
||||||
|
if strings.Contains(arg, " ") {
|
||||||
|
args[i] = fmt.Sprintf("\"%s\"", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, argstring = splitInput(strings.Join(args, " "))
|
||||||
|
|
||||||
|
// If alias was found, use command alias
|
||||||
|
argstring = util.MustDoString(s.runtime, fmt.Sprintf(`return hilbish.aliases.resolve("%s")`, argstring)).AsString()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
args, err = shell.Fields(argstring, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If command is defined in Lua then run it
|
||||||
|
luacmdArgs := rt.NewTable()
|
||||||
|
for i, str := range args[1:] {
|
||||||
|
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := interp.HandlerCtx(ctx)
|
||||||
|
|
||||||
|
cmds := make(map[string]*rt.Closure)
|
||||||
|
luaCmds := util.MustDoString(s.runtime, "local commander = require 'commander'; return commander.registry()").AsTable()
|
||||||
|
util.ForEach(luaCmds, func(k, v rt.Value) {
|
||||||
|
cmds[k.AsString()] = k.AsTable().Get(rt.StringValue("exec")).AsClosure()
|
||||||
|
})
|
||||||
|
if cmd := cmds[args[0]]; cmd != nil {
|
||||||
|
stdin := sink.NewSinkInput(s.runtime, hc.Stdin)
|
||||||
|
stdout := sink.NewSinkOutput(s.runtime, hc.Stdout)
|
||||||
|
stderr := sink.NewSinkOutput(s.runtime, hc.Stderr)
|
||||||
|
|
||||||
|
sinks := rt.NewTable()
|
||||||
|
sinks.Set(rt.StringValue("in"), rt.UserDataValue(stdin.UserData))
|
||||||
|
sinks.Set(rt.StringValue("input"), rt.UserDataValue(stdin.UserData))
|
||||||
|
sinks.Set(rt.StringValue("out"), rt.UserDataValue(stdout.UserData))
|
||||||
|
sinks.Set(rt.StringValue("err"), rt.UserDataValue(stderr.UserData))
|
||||||
|
|
||||||
|
t := rt.NewThread(s.runtime)
|
||||||
|
sig := make(chan os.Signal)
|
||||||
|
exit := make(chan bool)
|
||||||
|
|
||||||
|
luaexitcode := rt.IntValue(63)
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
exit <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
signal.Notify(sig, os.Interrupt)
|
||||||
|
select {
|
||||||
|
case <-sig:
|
||||||
|
t.KillContext()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
luaexitcode, err = rt.Call1(t, rt.FunctionValue(cmd), rt.TableValue(luacmdArgs), rt.TableValue(sinks))
|
||||||
|
exit <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-exit
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||||
|
return interp.NewExitStatus(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitcode uint8
|
||||||
|
|
||||||
|
if code, ok := luaexitcode.TryInt(); ok {
|
||||||
|
exitcode = uint8(code)
|
||||||
|
} else if luaexitcode != rt.NilValue {
|
||||||
|
// deregister commander
|
||||||
|
delete(cmds, args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp.NewExitStatus(exitcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := util.LookPath(args[0])
|
||||||
|
if err == util.ErrNotExec {
|
||||||
|
return util.ExecError{
|
||||||
|
Typ: "not-executable",
|
||||||
|
Cmd: args[0],
|
||||||
|
Code: 126,
|
||||||
|
Colon: true,
|
||||||
|
Err: util.ErrNotExec,
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return util.ExecError{
|
||||||
|
Typ: "not-found",
|
||||||
|
Cmd: args[0],
|
||||||
|
Code: 127,
|
||||||
|
Err: util.ErrNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
killTimeout := 2 * time.Second
|
||||||
|
// from here is basically copy-paste of the default exec handler from
|
||||||
|
// sh/interp but with our job handling
|
||||||
|
|
||||||
|
env := hc.Env
|
||||||
|
envList := os.Environ()
|
||||||
|
env.Each(func(name string, vr expand.Variable) bool {
|
||||||
|
if vr.Exported && vr.Kind == expand.String {
|
||||||
|
envList = append(envList, name+"="+vr.String())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd := exec.Cmd{
|
||||||
|
Path: path,
|
||||||
|
Args: args,
|
||||||
|
Env: envList,
|
||||||
|
Dir: hc.Dir,
|
||||||
|
Stdin: hc.Stdin,
|
||||||
|
Stdout: hc.Stdout,
|
||||||
|
Stderr: hc.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
//var j *job
|
||||||
|
if bg {
|
||||||
|
/*
|
||||||
|
j = jobs.getLatest()
|
||||||
|
j.setHandle(&cmd)
|
||||||
|
err = j.start()
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
err = cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
|
||||||
|
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: don't temporarily leak this goroutine
|
||||||
|
// if the program stops itself with the
|
||||||
|
// interrupt.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(killTimeout)
|
||||||
|
cmd.Process.Signal(os.Kill)
|
||||||
|
}()
|
||||||
|
cmd.Process.Signal(os.Interrupt)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit := util.HandleExecErr(err)
|
||||||
|
|
||||||
|
if bg {
|
||||||
|
//j.exitCode = int(exit)
|
||||||
|
//j.finish()
|
||||||
|
}
|
||||||
|
return interp.NewExitStatus(exit)
|
||||||
|
})(s.runner)
|
||||||
|
err = s.runner.Run(context.TODO(), stmt)
|
||||||
|
if err != nil {
|
||||||
|
return bg, strms.Stdout, strms.Stderr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bg, strms.Stdout, strms.Stderr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitInput(input string) ([]string, string) {
|
||||||
|
// end my suffering
|
||||||
|
// TODO: refactor this garbage
|
||||||
|
quoted := false
|
||||||
|
cmdArgs := []string{}
|
||||||
|
sb := &strings.Builder{}
|
||||||
|
cmdstr := &strings.Builder{}
|
||||||
|
|
||||||
|
for _, r := range input {
|
||||||
|
if r == '"' {
|
||||||
|
// start quoted input
|
||||||
|
// this determines if other runes are replaced
|
||||||
|
quoted = !quoted
|
||||||
|
// dont add back quotes
|
||||||
|
//sb.WriteRune(r)
|
||||||
|
} else if !quoted && r == '~' {
|
||||||
|
// if not in quotes and ~ is found then make it $HOME
|
||||||
|
sb.WriteString(os.Getenv("HOME"))
|
||||||
|
} else if !quoted && r == ' ' {
|
||||||
|
// if not quoted and there's a space then add to cmdargs
|
||||||
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
} else {
|
||||||
|
sb.WriteRune(r)
|
||||||
|
}
|
||||||
|
cmdstr.WriteRune(r)
|
||||||
|
}
|
||||||
|
if sb.Len() > 0 {
|
||||||
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdArgs, cmdstr.String()
|
||||||
|
}
|
2
job.go
2
job.go
|
@ -136,7 +136,7 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
if !j.running {
|
if !j.running {
|
||||||
err := j.start()
|
err := j.start()
|
||||||
exit := handleExecErr(err)
|
exit := util.HandleExecErr(err)
|
||||||
j.exitCode = int(exit)
|
j.exitCode = int(exit)
|
||||||
j.finish()
|
j.finish()
|
||||||
}
|
}
|
||||||
|
|
6
lua.go
6
lua.go
|
@ -4,10 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"hilbish/sink"
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
"hilbish/golibs/bait"
|
"hilbish/golibs/bait"
|
||||||
"hilbish/golibs/commander"
|
"hilbish/golibs/commander"
|
||||||
"hilbish/golibs/fs"
|
"hilbish/golibs/fs"
|
||||||
|
"hilbish/golibs/snail"
|
||||||
"hilbish/golibs/terminal"
|
"hilbish/golibs/terminal"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
@ -23,15 +25,15 @@ func luaInit() {
|
||||||
MessageHandler: debuglib.Traceback,
|
MessageHandler: debuglib.Traceback,
|
||||||
})
|
})
|
||||||
lib.LoadAll(l)
|
lib.LoadAll(l)
|
||||||
setupSinkType(l)
|
sink.SetupSinkType(l)
|
||||||
|
|
||||||
lib.LoadLibs(l, hilbishLoader)
|
lib.LoadLibs(l, hilbishLoader)
|
||||||
// yes this is stupid, i know
|
// yes this is stupid, i know
|
||||||
util.DoString(l, "hilbish = require 'hilbish'")
|
util.DoString(l, "hilbish = require 'hilbish'")
|
||||||
|
|
||||||
// Add fs and terminal module module to Lua
|
|
||||||
lib.LoadLibs(l, fs.Loader)
|
lib.LoadLibs(l, fs.Loader)
|
||||||
lib.LoadLibs(l, terminal.Loader)
|
lib.LoadLibs(l, terminal.Loader)
|
||||||
|
lib.LoadLibs(l, snail.Loader)
|
||||||
|
|
||||||
cmds = commander.New(l)
|
cmds = commander.New(l)
|
||||||
lib.LoadLibs(l, cmds.Loader)
|
lib.LoadLibs(l, cmds.Loader)
|
||||||
|
|
10
main.go
10
main.go
|
@ -21,7 +21,6 @@ import (
|
||||||
"github.com/pborman/getopt"
|
"github.com/pborman/getopt"
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -311,15 +310,6 @@ func removeDupes(slice []string) []string {
|
||||||
return newSlice
|
return newSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if strings.ToLower(a) == strings.ToLower(e) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func exit(code int) {
|
func exit(code int) {
|
||||||
jobs.stopAll()
|
jobs.stopAll()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
--- hilbish.runner
|
--- hilbish.runner
|
||||||
|
local snail = require 'snail'
|
||||||
|
|
||||||
local currentRunner = 'hybrid'
|
local currentRunner = 'hybrid'
|
||||||
local runners = {}
|
local runners = {}
|
||||||
|
|
||||||
|
@ -107,7 +109,5 @@ hilbish.runner.add('lua', function(input)
|
||||||
return hilbish.runner.lua(cmdStr)
|
return hilbish.runner.lua(cmdStr)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
hilbish.runner.add('sh', function(input)
|
hilbish.runner.add('sh', snail.new())
|
||||||
return hilbish.runner.sh(input)
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ end)
|
||||||
*/
|
*/
|
||||||
func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
exports := map[string]util.LuaExport{
|
exports := map[string]util.LuaExport{
|
||||||
"sh": {shRunner, 1, false},
|
//"sh": {shRunner, 1, false},
|
||||||
"lua": {luaRunner, 1, false},
|
"lua": {luaRunner, 1, false},
|
||||||
"setMode": {hlrunnerMode, 1, false},
|
"setMode": {hlrunnerMode, 1, false},
|
||||||
}
|
}
|
||||||
|
@ -66,10 +66,12 @@ func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
|
|
||||||
// #interface runner
|
// #interface runner
|
||||||
// setMode(cb)
|
// setMode(cb)
|
||||||
|
// **NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
// Use `hilbish.runner.setCurrent` instead.
|
||||||
// This is the same as the `hilbish.runnerMode` function.
|
// This is the same as the `hilbish.runnerMode` function.
|
||||||
// It takes a callback, which will be used to execute all interactive input.
|
// It takes a callback, which will be used to execute all interactive input.
|
||||||
// In normal cases, neither callbacks should be overrided by the user,
|
// In normal cases, neither callbacks should be overrided by the user,
|
||||||
// as the higher level functions listed below this will handle it.
|
// as the higher level functions (setCurrent) this will handle it.
|
||||||
// #param cb function
|
// #param cb function
|
||||||
func _runnerMode() {}
|
func _runnerMode() {}
|
||||||
|
|
||||||
|
@ -78,6 +80,7 @@ func _runnerMode() {}
|
||||||
// Runs a command in Hilbish's shell script interpreter.
|
// Runs a command in Hilbish's shell script interpreter.
|
||||||
// This is the equivalent of using `source`.
|
// This is the equivalent of using `source`.
|
||||||
// #param cmd string
|
// #param cmd string
|
||||||
|
/*
|
||||||
func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -101,6 +104,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil
|
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// #interface runner
|
// #interface runner
|
||||||
// lua(cmd)
|
// lua(cmd)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package sink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -17,15 +17,15 @@ var sinkMetaKey = rt.StringValue("hshsink")
|
||||||
// #type
|
// #type
|
||||||
// A sink is a structure that has input and/or output to/from
|
// A sink is a structure that has input and/or output to/from
|
||||||
// a desination.
|
// a desination.
|
||||||
type sink struct{
|
type Sink struct{
|
||||||
writer *bufio.Writer
|
writer *bufio.Writer
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
file *os.File
|
file *os.File
|
||||||
ud *rt.UserData
|
UserData *rt.UserData
|
||||||
autoFlush bool
|
autoFlush bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSinkType(rtm *rt.Runtime) {
|
func SetupSinkType(rtm *rt.Runtime) {
|
||||||
sinkMeta := rt.NewTable()
|
sinkMeta := rt.NewTable()
|
||||||
|
|
||||||
sinkMethods := rt.NewTable()
|
sinkMethods := rt.NewTable()
|
||||||
|
@ -37,7 +37,7 @@ func setupSinkType(rtm *rt.Runtime) {
|
||||||
"write": {luaSinkWrite, 2, false},
|
"write": {luaSinkWrite, 2, false},
|
||||||
"writeln": {luaSinkWriteln, 2, false},
|
"writeln": {luaSinkWriteln, 2, false},
|
||||||
}
|
}
|
||||||
util.SetExports(l, sinkMethods, sinkFuncs)
|
util.SetExports(rtm, sinkMethods, sinkFuncs)
|
||||||
|
|
||||||
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
s, _ := sinkArg(c, 0)
|
s, _ := sinkArg(c, 0)
|
||||||
|
@ -64,7 +64,7 @@ func setupSinkType(rtm *rt.Runtime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
|
sinkMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(sinkIndex, "__index", 2, false)))
|
||||||
l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
rtm.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -212,11 +212,11 @@ func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSinkInput(r io.Reader) *sink {
|
func NewSinkInput(rtm *rt.Runtime, r io.Reader) *Sink {
|
||||||
s := &sink{
|
s := &Sink{
|
||||||
reader: bufio.NewReader(r),
|
reader: bufio.NewReader(r),
|
||||||
}
|
}
|
||||||
s.ud = sinkUserData(s)
|
s.UserData = sinkUserData(rtm, s)
|
||||||
|
|
||||||
if f, ok := r.(*os.File); ok {
|
if f, ok := r.(*os.File); ok {
|
||||||
s.file = f
|
s.file = f
|
||||||
|
@ -225,17 +225,17 @@ func newSinkInput(r io.Reader) *sink {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSinkOutput(w io.Writer) *sink {
|
func NewSinkOutput(rtm *rt.Runtime, w io.Writer) *Sink {
|
||||||
s := &sink{
|
s := &Sink{
|
||||||
writer: bufio.NewWriter(w),
|
writer: bufio.NewWriter(w),
|
||||||
autoFlush: true,
|
autoFlush: true,
|
||||||
}
|
}
|
||||||
s.ud = sinkUserData(s)
|
s.UserData = sinkUserData(rtm, s)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func sinkArg(c *rt.GoCont, arg int) (*sink, error) {
|
func sinkArg(c *rt.GoCont, arg int) (*Sink, error) {
|
||||||
s, ok := valueToSink(c.Arg(arg))
|
s, ok := valueToSink(c.Arg(arg))
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("#%d must be a sink", arg + 1)
|
return nil, fmt.Errorf("#%d must be a sink", arg + 1)
|
||||||
|
@ -244,17 +244,17 @@ func sinkArg(c *rt.GoCont, arg int) (*sink, error) {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueToSink(val rt.Value) (*sink, bool) {
|
func valueToSink(val rt.Value) (*Sink, bool) {
|
||||||
u, ok := val.TryUserData()
|
u, ok := val.TryUserData()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
s, ok := u.Value().(*sink)
|
s, ok := u.Value().(*Sink)
|
||||||
return s, ok
|
return s, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func sinkUserData(s *sink) *rt.UserData {
|
func sinkUserData(rtm *rt.Runtime, s *Sink) *rt.UserData {
|
||||||
sinkMeta := l.Registry(sinkMetaKey)
|
sinkMeta := rtm.Registry(sinkMetaKey)
|
||||||
return rt.NewUserData(s, sinkMeta.AsTable())
|
return rt.NewUserData(s, sinkMeta.AsTable())
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Streams struct {
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
Stdin io.Reader
|
||||||
|
}
|
118
util/util.go
118
util/util.go
|
@ -2,14 +2,78 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNotExec = errors.New("not executable")
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
type ExecError struct{
|
||||||
|
Typ string
|
||||||
|
Cmd string
|
||||||
|
Code int
|
||||||
|
Colon bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExecError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.Cmd, e.Typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExecError) sprint() error {
|
||||||
|
sep := " "
|
||||||
|
if e.Colon {
|
||||||
|
sep = ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("hilbish: %s%s%s", e.Cmd, sep, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsExecError(err error) (ExecError, bool) {
|
||||||
|
if exErr, ok := err.(ExecError); ok {
|
||||||
|
return exErr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(err.Error(), ": ")
|
||||||
|
knownTypes := []string{
|
||||||
|
"not-found",
|
||||||
|
"not-executable",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fields) > 1 && Contains(knownTypes, fields[1]) {
|
||||||
|
var colon bool
|
||||||
|
var e error
|
||||||
|
switch fields[1] {
|
||||||
|
case "not-found":
|
||||||
|
e = ErrNotFound
|
||||||
|
case "not-executable":
|
||||||
|
colon = true
|
||||||
|
e = ErrNotExec
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecError{
|
||||||
|
Cmd: fields[0],
|
||||||
|
Typ: fields[1],
|
||||||
|
Colon: colon,
|
||||||
|
Err: e,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecError{}, false
|
||||||
|
}
|
||||||
|
|
||||||
// SetField sets a field in a table, adding docs for it.
|
// SetField sets a field in a table, adding docs for it.
|
||||||
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||||
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value) {
|
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value) {
|
||||||
|
@ -36,6 +100,15 @@ func DoString(rtm *rt.Runtime, code string) (rt.Value, error) {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustDoString(rtm *rt.Runtime, code string) rt.Value {
|
||||||
|
val, err := DoString(rtm, code)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// DoFile runs the contents of the file in the Lua runtime.
|
// DoFile runs the contents of the file in the Lua runtime.
|
||||||
func DoFile(rtm *rt.Runtime, path string) error {
|
func DoFile(rtm *rt.Runtime, path string) error {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
@ -151,13 +224,13 @@ func LookPath(file string) (string, error) { // custom lookpath function so we k
|
||||||
}
|
}
|
||||||
for _, s := range skip {
|
for _, s := range skip {
|
||||||
if strings.HasPrefix(file, s) {
|
if strings.HasPrefix(file, s) {
|
||||||
return file, findExecutable(file, false, false)
|
return file, FindExecutable(file, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||||
path := filepath.Join(dir, file)
|
path := filepath.Join(dir, file)
|
||||||
err := findExecutable(path, true, false)
|
err := FindExecutable(path, true, false)
|
||||||
if err == errNotExec {
|
if err == ErrNotExec {
|
||||||
return "", err
|
return "", err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return path, nil
|
return path, nil
|
||||||
|
@ -166,3 +239,42 @@ func LookPath(file string) (string, error) { // custom lookpath function so we k
|
||||||
|
|
||||||
return "", os.ErrNotExist
|
return "", os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Contains(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if strings.ToLower(a) == strings.ToLower(e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleExecErr(err error) (exit uint8) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
switch x := err.(type) {
|
||||||
|
case *exec.ExitError:
|
||||||
|
// started, but errored - default to 1 if OS
|
||||||
|
// doesn't have exit statuses
|
||||||
|
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||||
|
if status.Signaled() {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = uint8(128 + status.Signal())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = uint8(status.ExitStatus())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exit = 1
|
||||||
|
return
|
||||||
|
case *exec.Error:
|
||||||
|
// did not start
|
||||||
|
//fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||||
|
exit = 127
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -20,5 +20,5 @@ func FindExecutable(path string, inPath, dirs bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue