mirror of
https://github.com/Hilbis/Hilbish
synced 2025-04-16 10:33:23 +00:00
refactor: decouple sh use in core exec code (#337)
This commit is contained in:
parent
fe4e972fbe
commit
02c89b99dd
152
api.go
152
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{
|
||||||
@ -39,7 +38,6 @@ var exports = map[string]util.LuaExport{
|
|||||||
"complete": {hlcomplete, 2, false},
|
"complete": {hlcomplete, 2, false},
|
||||||
"cwd": {hlcwd, 0, false},
|
"cwd": {hlcwd, 0, false},
|
||||||
"exec": {hlexec, 1, false},
|
"exec": {hlexec, 1, false},
|
||||||
"runnerMode": {hlrunnerMode, 1, false},
|
|
||||||
"goro": {hlgoro, 1, true},
|
"goro": {hlgoro, 1, true},
|
||||||
"highlighter": {hlhighlighter, 1, false},
|
"highlighter": {hlhighlighter, 1, false},
|
||||||
"hinter": {hlhinter, 1, false},
|
"hinter": {hlhinter, 1, false},
|
||||||
@ -49,7 +47,6 @@ 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},
|
|
||||||
"timeout": {hltimeout, 2, false},
|
"timeout": {hltimeout, 2, false},
|
||||||
"which": {hlwhich, 1, false},
|
"which": {hlwhich, 1, false},
|
||||||
}
|
}
|
||||||
@ -134,6 +131,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
pluginModule := moduleLoader(rtm)
|
pluginModule := moduleLoader(rtm)
|
||||||
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
||||||
|
|
||||||
|
sinkModule := util.SinkLoader(l)
|
||||||
|
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
|
||||||
|
|
||||||
return rt.TableValue(mod), nil
|
return rt.TableValue(mod), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +154,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,112 +183,7 @@ func handleStream(v rt.Value, strms *streams, errStream bool) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// cwd() -> string
|
||||||
// Returns the current directory of the shell.
|
// Returns the current directory of the shell.
|
||||||
@ -404,7 +300,7 @@ hilbish.multiprompt '-->'
|
|||||||
*/
|
*/
|
||||||
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func hlmultiprompt(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 c.PushingNext1(t.Runtime, rt.StringValue(multilinePrompt)), nil
|
||||||
}
|
}
|
||||||
prompt, err := c.StringArg(0)
|
prompt, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -508,7 +404,7 @@ func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
cmdArgs, _ := splitInput(cmd)
|
cmdArgs, _ := splitInput(cmd)
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
cmdPath, err := exec.LookPath(cmdArgs[0])
|
cmdPath, err := util.LookPath(cmdArgs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
// if we get here, cmdPath will be nothing
|
// if we get here, cmdPath will be nothing
|
||||||
@ -706,7 +602,7 @@ func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath(cmd)
|
path, err := util.LookPath(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
@ -742,34 +638,6 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runnerMode(mode)
|
|
||||||
// Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
// This determines whether Hilbish wll try to run input as Lua
|
|
||||||
// and/or sh or only do one of either.
|
|
||||||
// Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
// sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
// will call it to execute user input instead.
|
|
||||||
// Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
// #param mode string|function
|
|
||||||
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mode := c.Arg(0)
|
|
||||||
|
|
||||||
switch mode.Type() {
|
|
||||||
case rt.StringType:
|
|
||||||
switch mode.AsString() {
|
|
||||||
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
|
||||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
|
||||||
}
|
|
||||||
case rt.FunctionType: runnerMode = mode
|
|
||||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.TypeName())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hinter(line, pos)
|
// hinter(line, pos)
|
||||||
// The command line hint handler. It gets called on every key insert to
|
// The command line hint handler. It gets called on every key insert to
|
||||||
// determine what text to use as an inline hint. It is passed the current
|
// determine what text to use as an inline hint. It is passed the current
|
||||||
|
@ -84,6 +84,7 @@ var prefix = map[string]string{
|
|||||||
"commander": "c",
|
"commander": "c",
|
||||||
"bait": "b",
|
"bait": "b",
|
||||||
"terminal": "term",
|
"terminal": "term",
|
||||||
|
"snail": "snail",
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||||
@ -208,6 +209,10 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupDoc(mod string, fun *doc.Func) *docPiece {
|
func setupDoc(mod string, fun *doc.Func) *docPiece {
|
||||||
|
if fun.Doc == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
docs := strings.TrimSpace(fun.Doc)
|
docs := strings.TrimSpace(fun.Doc)
|
||||||
tags, parts := getTagsAndDocs(docs)
|
tags, parts := getTagsAndDocs(docs)
|
||||||
|
|
||||||
@ -320,7 +325,7 @@ provided by Hilbish.
|
|||||||
|
|
||||||
os.Mkdir("emmyLuaDocs", 0777)
|
os.Mkdir("emmyLuaDocs", 0777)
|
||||||
|
|
||||||
dirs := []string{"./"}
|
dirs := []string{"./", "./util"}
|
||||||
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
|
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
@ -347,7 +352,7 @@ provided by Hilbish.
|
|||||||
pieces := []docPiece{}
|
pieces := []docPiece{}
|
||||||
typePieces := []docPiece{}
|
typePieces := []docPiece{}
|
||||||
mod := l
|
mod := l
|
||||||
if mod == "main" {
|
if mod == "main" || mod == "util" {
|
||||||
mod = "hilbish"
|
mod = "hilbish"
|
||||||
}
|
}
|
||||||
var hasInterfaces bool
|
var hasInterfaces bool
|
||||||
@ -431,14 +436,23 @@ provided by Hilbish.
|
|||||||
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
|
interfaceModules[modname].Types = append(interfaceModules[modname].Types, piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
docs[mod] = module{
|
fmt.Println(filteredTypePieces)
|
||||||
Types: filteredTypePieces,
|
if newDoc, ok := docs[mod]; ok {
|
||||||
Docs: filteredPieces,
|
oldMod := docs[mod]
|
||||||
ShortDescription: shortDesc,
|
newDoc.Types = append(filteredTypePieces, oldMod.Types...)
|
||||||
Description: strings.Join(desc, "\n"),
|
newDoc.Docs = append(filteredPieces, oldMod.Docs...)
|
||||||
HasInterfaces: hasInterfaces,
|
|
||||||
Properties: docPieceTag("property", tags),
|
docs[mod] = newDoc
|
||||||
Fields: docPieceTag("field", tags),
|
} else {
|
||||||
|
docs[mod] = module{
|
||||||
|
Types: filteredTypePieces,
|
||||||
|
Docs: filteredPieces,
|
||||||
|
ShortDescription: shortDesc,
|
||||||
|
Description: strings.Join(desc, "\n"),
|
||||||
|
HasInterfaces: hasInterfaces,
|
||||||
|
Properties: docPieceTag("property", tags),
|
||||||
|
Fields: docPieceTag("field", tags),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ for _, fname in ipairs(files) do
|
|||||||
local mod = header:match(modpattern)
|
local mod = header:match(modpattern)
|
||||||
if not mod then goto continue end
|
if not mod then goto continue end
|
||||||
|
|
||||||
print(fname, mod)
|
|
||||||
pieces[mod] = {}
|
pieces[mod] = {}
|
||||||
descriptions[mod] = {}
|
descriptions[mod] = {}
|
||||||
|
|
||||||
@ -42,10 +41,12 @@ for _, fname in ipairs(files) do
|
|||||||
|
|
||||||
local dps = {
|
local dps = {
|
||||||
description = {},
|
description = {},
|
||||||
|
example = {},
|
||||||
params = {}
|
params = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
local offset = 1
|
local offset = 1
|
||||||
|
local doingExample = false
|
||||||
while true do
|
while true do
|
||||||
local prev = lines[lineno - offset]
|
local prev = lines[lineno - offset]
|
||||||
|
|
||||||
@ -59,17 +60,25 @@ for _, fname in ipairs(files) do
|
|||||||
|
|
||||||
if emmy then
|
if emmy then
|
||||||
if emmy == 'param' then
|
if emmy == 'param' then
|
||||||
print('bruh', emmythings[1], emmythings[2])
|
|
||||||
table.insert(dps.params, 1, {
|
table.insert(dps.params, 1, {
|
||||||
name = emmythings[1],
|
name = emmythings[1],
|
||||||
type = emmythings[2],
|
type = emmythings[2],
|
||||||
-- the +1 accounts for space.
|
-- the +1 accounts for space.
|
||||||
description = table.concat(emmythings, ' '):sub(emmythings[1]:len() + 1 + emmythings[2]:len() + 1)
|
description = table.concat(emmythings, ' '):sub(emmythings[1]:len() + 1 + emmythings[2]:len() + 1)
|
||||||
})
|
})
|
||||||
print(table.concat(emmythings, '/'))
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(dps.description, 1, docline)
|
if docline:match '#example' then
|
||||||
|
doingExample = not doingExample
|
||||||
|
end
|
||||||
|
|
||||||
|
if not docline:match '#example' then
|
||||||
|
if doingExample then
|
||||||
|
table.insert(dps.example, 1, docline)
|
||||||
|
else
|
||||||
|
table.insert(dps.description, 1, docline)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
else
|
else
|
||||||
@ -77,7 +86,7 @@ for _, fname in ipairs(files) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
pieces[mod][funcName] = dps
|
table.insert(pieces[mod], {funcName, dps})
|
||||||
end
|
end
|
||||||
docPiece = {}
|
docPiece = {}
|
||||||
goto continue2
|
goto continue2
|
||||||
@ -109,11 +118,15 @@ for iface, dps in pairs(pieces) do
|
|||||||
docParent = "API"
|
docParent = "API"
|
||||||
path = string.format('docs/api/%s/%s.md', mod, iface)
|
path = string.format('docs/api/%s/%s.md', mod, iface)
|
||||||
end
|
end
|
||||||
|
if iface == 'hilbish' then
|
||||||
|
docParent = "API"
|
||||||
|
path = string.format('docs/api/hilbish/_index.md', mod, iface)
|
||||||
|
end
|
||||||
|
|
||||||
fs.mkdir(fs.dir(path), true)
|
fs.mkdir(fs.dir(path), true)
|
||||||
|
|
||||||
local exists = pcall(fs.stat, path)
|
local exists = pcall(fs.stat, path)
|
||||||
local newOrNotNature = exists and mod ~= 'nature'
|
local newOrNotNature = (exists and mod ~= 'nature') or iface == 'hilbish'
|
||||||
|
|
||||||
local f <close> = io.open(path, newOrNotNature and 'r+' or 'w+')
|
local f <close> = io.open(path, newOrNotNature and 'r+' or 'w+')
|
||||||
local tocPos
|
local tocPos
|
||||||
@ -129,9 +142,6 @@ for iface, dps in pairs(pieces) do
|
|||||||
tocPos = f:seek()
|
tocPos = f:seek()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
print(f)
|
|
||||||
|
|
||||||
print('mod and path:', mod, path)
|
|
||||||
|
|
||||||
local tocSearch = false
|
local tocSearch = false
|
||||||
for line in f:lines() do
|
for line in f:lines() do
|
||||||
@ -144,7 +154,10 @@ for iface, dps in pairs(pieces) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for func, docs in pairs(dps) do
|
table.sort(dps, function(a, b) return a[1] < b[1] end)
|
||||||
|
for _, piece in pairs(dps) do
|
||||||
|
local func = piece[1]
|
||||||
|
local docs = piece[2]
|
||||||
local sig = string.format('%s.%s(', iface, func)
|
local sig = string.format('%s.%s(', iface, func)
|
||||||
local params = ''
|
local params = ''
|
||||||
for idx, param in ipairs(docs.params) do
|
for idx, param in ipairs(docs.params) do
|
||||||
@ -186,6 +199,10 @@ for iface, dps in pairs(pieces) do
|
|||||||
f:write(string.format('`%s` **`%s`** \n', param.name:gsub('%?$', ''), param.type))
|
f:write(string.format('`%s` **`%s`** \n', param.name:gsub('%?$', ''), param.type))
|
||||||
f:write(string.format('%s\n\n', param.description))
|
f:write(string.format('%s\n\n', param.description))
|
||||||
end
|
end
|
||||||
|
if #docs.example ~= 0 then
|
||||||
|
f:write '#### Example\n'
|
||||||
|
f:write(string.format('```lua\n%s\n```\n', table.concat(docs.example, '\n')))
|
||||||
|
end
|
||||||
--[[
|
--[[
|
||||||
local params = table.filter(docs, function(t)
|
local params = table.filter(docs, function(t)
|
||||||
return t:match '^%-%-%- @param'
|
return t:match '^%-%-%- @param'
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,10 @@ interfaces and functions which directly relate to shell functionality.
|
|||||||
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|
|<a href="#prependPath">prependPath(dir)</a>|Prepends `dir` to $PATH.|
|
||||||
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|
|<a href="#prompt">prompt(str, typ)</a>|Changes the shell prompt to the provided string.|
|
||||||
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|
|<a href="#read">read(prompt) -> input (string)</a>|Read input from the user, using Hilbish's line editor/input reader.|
|
||||||
|<a href="#run">run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
|
||||||
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
|
||||||
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|
|<a href="#timeout">timeout(cb, time) -> @Timer</a>|Executed the `cb` function after a period of `time`.|
|
||||||
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
|
|<a href="#which">which(name) -> string</a>|Checks if `name` is a valid command.|
|
||||||
|
|<a href="#runnerMode">runnerMode(mode)</a>|Sets the execution/runner mode for interactive Hilbish.|
|
||||||
|
|<a href="#run">run(cmd, streams)</a>|Runs `cmd` in Hilbish's shell script interpreter.|
|
||||||
|
|
||||||
## Static module fields
|
## Static module fields
|
||||||
|||
|
|||
|
||||||
@ -408,72 +408,6 @@ Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
|||||||
`string` **`prompt?`**
|
`string` **`prompt?`**
|
||||||
Text to print before input, can be empty.
|
Text to print before input, can be empty.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='run'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
hilbish.run(cmd, streams) -> exitCode (number), stdout (string), stderr (string)
|
|
||||||
<a href="#run" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`string` **`cmd`**
|
|
||||||
|
|
||||||
|
|
||||||
`table|boolean` **`streams`**
|
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
```lua
|
|
||||||
|
|
||||||
// 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
|
|
||||||
})
|
|
||||||
|
|
||||||
```
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='runnerMode'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
hilbish.runnerMode(mode)
|
|
||||||
<a href="#runnerMode" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
This determines whether Hilbish wll try to run input as Lua
|
|
||||||
and/or sh or only do one of either.
|
|
||||||
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
will call it to execute user input instead.
|
|
||||||
Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`string|function` **`mode`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@ -519,8 +453,7 @@ Will return the path of the binary, or a basename if it's a commander.
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
## Sink
|
## Sink
|
||||||
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.
|
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
#### autoFlush(auto)
|
#### autoFlush(auto)
|
||||||
@ -542,3 +475,65 @@ Writes data to a sink.
|
|||||||
#### writeln(str)
|
#### writeln(str)
|
||||||
Writes data to a sink with a newline at the end.
|
Writes data to a sink with a newline at the end.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='run'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.run(cmd, streams)
|
||||||
|
<a href="#run" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
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.
|
||||||
|
#### Parameters
|
||||||
|
`cmd` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`streams` **`table|boolean`**
|
||||||
|
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```lua
|
||||||
|
-- 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
|
||||||
|
})
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='runnerMode'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runnerMode(mode)
|
||||||
|
<a href="#runnerMode" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets the execution/runner mode for interactive Hilbish.
|
||||||
|
**NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
Use `hilbish.runner.setCurrent` instead.
|
||||||
|
This determines whether Hilbish wll try to run input as Lua
|
||||||
|
and/or sh or only do one of either.
|
||||||
|
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||||
|
sh, and lua. It also accepts a function, to which if it is passed one
|
||||||
|
will call it to execute user input instead.
|
||||||
|
Read [about runner mode](../features/runner-mode) for more information.
|
||||||
|
#### Parameters
|
||||||
|
`mode` **`string|function`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
135
docs/api/hilbish/hilbish.messages.md
Normal file
135
docs/api/hilbish/hilbish.messages.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
title: Module hilbish.messages
|
||||||
|
description: simplistic message passing
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
The messages interface defines a way for Hilbish-integrated commands,
|
||||||
|
user config and other tasks to send notifications to alert the user.z
|
||||||
|
The `hilbish.message` type is a table with the following keys:
|
||||||
|
`title` (string): A title for the message notification.
|
||||||
|
`text` (string): The contents of the message.
|
||||||
|
`channel` (string): States the origin of the message, `hilbish.*` is reserved for Hilbish tasks.
|
||||||
|
`summary` (string): A short summary of the `text`.
|
||||||
|
`icon` (string): Unicode (preferably standard emoji) icon for the message notification
|
||||||
|
`read` (boolean): Whether the full message has been read or not.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#unreadCount">unreadCount()</a>|Returns the amount of unread messages.|
|
||||||
|
|<a href="#send">send(message)</a>|Sends a message.|
|
||||||
|
|<a href="#readAll">readAll()</a>|Marks all messages as read.|
|
||||||
|
|<a href="#read">read(idx)</a>|Marks a message at `idx` as read.|
|
||||||
|
|<a href="#delete">delete(idx)</a>|Deletes the message at `idx`.|
|
||||||
|
|<a href="#clear">clear()</a>|Deletes all messages.|
|
||||||
|
|<a href="#all">all()</a>|Returns all messages.|
|
||||||
|
<hr>
|
||||||
|
<div id='all'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.all()
|
||||||
|
<a href="#all" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns all messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='clear'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.clear()
|
||||||
|
<a href="#clear" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Deletes all messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='delete'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.delete(idx)
|
||||||
|
<a href="#delete" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Deletes the message at `idx`.
|
||||||
|
#### Parameters
|
||||||
|
`idx` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='read'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.read(idx)
|
||||||
|
<a href="#read" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Marks a message at `idx` as read.
|
||||||
|
#### Parameters
|
||||||
|
`idx` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='readAll'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.readAll()
|
||||||
|
<a href="#readAll" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Marks all messages as read.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='send'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.send(message)
|
||||||
|
<a href="#send" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sends a message.
|
||||||
|
#### Parameters
|
||||||
|
`message` **`hilbish.message`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='unreadCount'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.messages.unreadCount()
|
||||||
|
<a href="#unreadCount" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns the amount of unread messages.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
@ -54,29 +54,16 @@ end)
|
|||||||
## Functions
|
## Functions
|
||||||
|||
|
|||
|
||||||
|----|----|
|
|----|----|
|
||||||
|<a href="#runner.setMode">setMode(cb)</a>|This is the same as the `hilbish.runnerMode` function.|
|
|
||||||
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|
|<a href="#runner.lua">lua(cmd)</a>|Evaluates `cmd` as Lua input. This is the same as using `dofile`|
|
||||||
|<a href="#runner.sh">sh(cmd)</a>|Runs a command in Hilbish's shell script interpreter.|
|
|<a href="#sh">sh()</a>|nil|
|
||||||
|
|<a href="#setMode">setMode(mode)</a>|**NOTE: This function is deprecated and will be removed in 3.0**|
|
||||||
<hr>
|
|<a href="#setCurrent">setCurrent(name)</a>|Sets Hilbish's runner mode by name.|
|
||||||
<div id='runner.setMode'>
|
|<a href="#set">set(name, runner)</a>|*Sets* a runner by name. The difference between this function and|
|
||||||
<h4 class='heading'>
|
|<a href="#run">run(input, priv)</a>|Runs `input` with the currently set Hilbish runner.|
|
||||||
hilbish.runner.setMode(cb)
|
|<a href="#getCurrent">getCurrent()</a>|Returns the current runner by name.|
|
||||||
<a href="#runner.setMode" class='heading-link'>
|
|<a href="#get">get(name)</a>|Get a runner by name.|
|
||||||
<i class="fas fa-paperclip"></i>
|
|<a href="#exec">exec(cmd, runnerName)</a>|Executes `cmd` with a runner.|
|
||||||
</a>
|
|<a href="#add">add(name, runner)</a>|Adds a runner to the table of available runners.|
|
||||||
</h4>
|
|
||||||
|
|
||||||
This is the same as the `hilbish.runnerMode` function.
|
|
||||||
It takes a callback, which will be used to execute all interactive input.
|
|
||||||
In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
as the higher level functions listed below this will handle it.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
`function` **`cb`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='runner.lua'>
|
<div id='runner.lua'>
|
||||||
@ -97,20 +84,164 @@ or `load`, but is appropriated for the runner interface.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='runner.sh'>
|
<div id='add'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
hilbish.runner.sh(cmd)
|
hilbish.runner.add(name, runner)
|
||||||
<a href="#runner.sh" class='heading-link'>
|
<a href="#add" class='heading-link'>
|
||||||
<i class="fas fa-paperclip"></i>
|
<i class="fas fa-paperclip"></i>
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
Runs a command in Hilbish's shell script interpreter.
|
Adds a runner to the table of available runners.
|
||||||
This is the equivalent of using `source`.
|
If runner is a table, it must have the run function in it.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
`string` **`cmd`**
|
`name` **`string`**
|
||||||
|
Name of the runner
|
||||||
|
|
||||||
|
`runner` **`function|table`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='exec'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.exec(cmd, runnerName)
|
||||||
|
<a href="#exec" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Executes `cmd` with a runner.
|
||||||
|
If `runnerName` is not specified, it uses the default Hilbish runner.
|
||||||
|
#### Parameters
|
||||||
|
`cmd` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`runnerName` **`string?`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='get'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.get(name)
|
||||||
|
<a href="#get" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Get a runner by name.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
Name of the runner to retrieve.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='getCurrent'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.getCurrent()
|
||||||
|
<a href="#getCurrent" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Returns the current runner by name.
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='run'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.run(input, priv)
|
||||||
|
<a href="#run" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Runs `input` with the currently set Hilbish runner.
|
||||||
|
This method is how Hilbish executes commands.
|
||||||
|
`priv` is an optional boolean used to state if the input should be saved to history.
|
||||||
|
#### Parameters
|
||||||
|
`input` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`priv` **`bool`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='set'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.set(name, runner)
|
||||||
|
<a href="#set" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
*Sets* a runner by name. The difference between this function and
|
||||||
|
add, is set will *not* check if the named runner exists.
|
||||||
|
The runner table must have the run function in it.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
`runner` **`table`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setCurrent'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.setCurrent(name)
|
||||||
|
<a href="#setCurrent" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets Hilbish's runner mode by name.
|
||||||
|
#### Parameters
|
||||||
|
`name` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setMode'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.setMode(mode)
|
||||||
|
<a href="#setMode" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
**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.
|
||||||
|
It takes a callback, which will be used to execute all interactive input.
|
||||||
|
Or a string which names the runner mode to use.
|
||||||
|
#### Parameters
|
||||||
|
`mode` **`string|function`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='sh'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
hilbish.runner.sh()
|
||||||
|
<a href="#sh" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
50
docs/api/snail.md
Normal file
50
docs/api/snail.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Module snail
|
||||||
|
description: shell script interpreter library
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||||
|
It's not very useful other than running shell scripts, which can be done with other
|
||||||
|
Hilbish functions.
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|||
|
||||||
|
|----|----|
|
||||||
|
|<a href="#new">new() -> @Snail</a>|Creates a new Snail instance.|
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='new'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
snail.new() -> <a href="/Hilbish/docs/api/snail/#snail" style="text-decoration: none;" id="lol">Snail</a>
|
||||||
|
<a href="#new" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Creates a new Snail instance.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
This function has no parameters.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Types
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
## Snail
|
||||||
|
A Snail is a shell script interpreter instance.
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
#### dir(path)
|
||||||
|
Changes the directory of the snail instance.
|
||||||
|
The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
|
||||||
|
#### run(command, streams)
|
||||||
|
Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
|
@ -43,5 +43,29 @@ The notification. The properties are defined in the link above.
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
## hilbish.cd
|
||||||
like yanking or pasting text. See `doc vim-mode actions` for more info.
|
Sent when the current directory of the shell is changed (via interactive means.)
|
||||||
|
If you are implementing a custom command that changes the directory of the shell,
|
||||||
|
you must throw this hook manually for correctness.
|
||||||
|
|
||||||
|
#### Variables
|
||||||
|
`string` **`path`**
|
||||||
|
Absolute path of the directory that was changed to.
|
||||||
|
|
||||||
|
`string` **`oldPath`**
|
||||||
|
Absolute path of the directory Hilbish *was* in.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
## hilbish.vimAction
|
||||||
|
Sent when the user does a "vim action," being something like yanking or pasting text.
|
||||||
|
See `doc vim-mode actions` for more info.
|
||||||
|
|
||||||
|
#### Variables
|
||||||
|
`string` **`actionName`**
|
||||||
|
Absolute path of the directory that was changed to.
|
||||||
|
|
||||||
|
`table` **`args`**
|
||||||
|
Table of args relating to the Vim action.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
@ -15,43 +15,11 @@ directories.
|
|||||||
## Functions
|
## Functions
|
||||||
|||
|
|||
|
||||||
|----|----|
|
|----|----|
|
||||||
|
|<a href="#setOld">setOld(d)</a>|Sets the old directory string.|
|
||||||
|<a href="#recent">recent(idx)</a>|Get entry from recent directories list based on index.|
|
|<a href="#recent">recent(idx)</a>|Get entry from recent directories list based on index.|
|
||||||
|
|<a href="#push">push(dir)</a>|Add `dir` to the recent directories list.|
|
||||||
|<a href="#pop">pop(num)</a>|Remove the specified amount of dirs from the recent directories list.|
|
|<a href="#pop">pop(num)</a>|Remove the specified amount of dirs from the recent directories list.|
|
||||||
|<a href="#peak">peak(num)</a>|Look at `num` amount of recent directories, starting from the latest.|
|
|<a href="#peak">peak(num)</a>|Look at `num` amount of recent directories, starting from the latest.|
|
||||||
|<a href="#push">push(dir)</a>|Add `dir` to the recent directories list.|
|
|
||||||
|<a href="#setOld">setOld(d)</a>|Sets the old directory string.|
|
|
||||||
<hr>
|
|
||||||
<div id='setOld'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
dirs.setOld(d)
|
|
||||||
<a href="#setOld" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Sets the old directory string.
|
|
||||||
#### Parameters
|
|
||||||
`d` **`string`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div id='push'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
dirs.push(dir)
|
|
||||||
<a href="#push" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Add `dir` to the recent directories list.
|
|
||||||
#### Parameters
|
|
||||||
`dir` **`string`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='peak'>
|
<div id='peak'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
@ -83,6 +51,22 @@ Remove the specified amount of dirs from the recent directories list.
|
|||||||
`num` **`number`**
|
`num` **`number`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='push'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
dirs.push(dir)
|
||||||
|
<a href="#push" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Add `dir` to the recent directories list.
|
||||||
|
#### Parameters
|
||||||
|
`dir` **`string`**
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@ -101,3 +85,19 @@ Get entry from recent directories list based on index.
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='setOld'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
dirs.setOld(d)
|
||||||
|
<a href="#setOld" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Sets the old directory string.
|
||||||
|
#### Parameters
|
||||||
|
`d` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@ -17,29 +17,9 @@ is by the Greenhouse pager.
|
|||||||
## Functions
|
## Functions
|
||||||
|||
|
|||
|
||||||
|----|----|
|
|----|----|
|
||||||
|
|<a href="#renderInfoBlock">renderInfoBlock(type, text)</a>|Renders an info block. An info block is a block of text with|
|
||||||
|<a href="#renderCodeBlock">renderCodeBlock(text)</a>|Assembles and renders a code block. This returns|
|
|<a href="#renderCodeBlock">renderCodeBlock(text)</a>|Assembles and renders a code block. This returns|
|
||||||
|<a href="#highlight">highlight(text)</a>|Performs basic Lua code highlighting.|
|
|<a href="#highlight">highlight(text)</a>|Performs basic Lua code highlighting.|
|
||||||
|<a href="#renderInfoBlock">renderInfoBlock(type, text)</a>|Renders an info block. An info block is a block of text with|
|
|
||||||
<hr>
|
|
||||||
<div id='renderInfoBlock'>
|
|
||||||
<h4 class='heading'>
|
|
||||||
doc.renderInfoBlock(type, text)
|
|
||||||
<a href="#renderInfoBlock" class='heading-link'>
|
|
||||||
<i class="fas fa-paperclip"></i>
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
Renders an info block. An info block is a block of text with
|
|
||||||
an icon and styled text block.
|
|
||||||
#### Parameters
|
|
||||||
`type` **`string`**
|
|
||||||
Type of info block. The only one specially styled is the `warning`.
|
|
||||||
|
|
||||||
`text` **`string`**
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id='highlight'>
|
<div id='highlight'>
|
||||||
<h4 class='heading'>
|
<h4 class='heading'>
|
||||||
@ -74,3 +54,23 @@ and styles it to resemble a code block.
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div id='renderInfoBlock'>
|
||||||
|
<h4 class='heading'>
|
||||||
|
doc.renderInfoBlock(type, text)
|
||||||
|
<a href="#renderInfoBlock" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Renders an info block. An info block is a block of text with
|
||||||
|
an icon and styled text block.
|
||||||
|
#### Parameters
|
||||||
|
`type` **`string`**
|
||||||
|
Type of info block. The only one specially styled is the `warning`.
|
||||||
|
|
||||||
|
`text` **`string`**
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@ -7,12 +7,6 @@ local hilbish = {}
|
|||||||
--- @param cmd string
|
--- @param cmd string
|
||||||
function hilbish.aliases.add(alias, cmd) end
|
function hilbish.aliases.add(alias, cmd) end
|
||||||
|
|
||||||
--- This is the same as the `hilbish.runnerMode` function.
|
|
||||||
--- It takes a callback, which will be used to execute all interactive input.
|
|
||||||
--- In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
--- as the higher level functions listed below this will handle it.
|
|
||||||
function hilbish.runner.setMode(cb) end
|
|
||||||
|
|
||||||
--- Returns the current input line.
|
--- Returns the current input line.
|
||||||
function hilbish.editor.getLine() end
|
function hilbish.editor.getLine() end
|
||||||
|
|
||||||
@ -131,24 +125,6 @@ function hilbish.prompt(str, typ) end
|
|||||||
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
--- Returns `input`, will be nil if Ctrl-D is pressed, or an error occurs.
|
||||||
function hilbish.read(prompt) end
|
function hilbish.read(prompt) end
|
||||||
|
|
||||||
--- 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.
|
|
||||||
---
|
|
||||||
function hilbish.run(cmd, streams) end
|
|
||||||
|
|
||||||
--- Sets the execution/runner mode for interactive Hilbish.
|
|
||||||
--- This determines whether Hilbish wll try to run input as Lua
|
|
||||||
--- and/or sh or only do one of either.
|
|
||||||
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
|
||||||
--- sh, and lua. It also accepts a function, to which if it is passed one
|
|
||||||
--- will call it to execute user input instead.
|
|
||||||
--- Read [about runner mode](../features/runner-mode) for more information.
|
|
||||||
function hilbish.runnerMode(mode) end
|
|
||||||
|
|
||||||
--- Executed the `cb` function after a period of `time`.
|
--- Executed the `cb` function after a period of `time`.
|
||||||
--- This creates a Timer that starts ticking immediately.
|
--- This creates a Timer that starts ticking immediately.
|
||||||
function hilbish.timeout(cb, time) end
|
function hilbish.timeout(cb, time) end
|
||||||
@ -168,28 +144,6 @@ function hilbish.jobs:foreground() end
|
|||||||
--- or `load`, but is appropriated for the runner interface.
|
--- or `load`, but is appropriated for the runner interface.
|
||||||
function hilbish.runner.lua(cmd) end
|
function hilbish.runner.lua(cmd) end
|
||||||
|
|
||||||
--- Sets/toggles the option of automatically flushing output.
|
|
||||||
--- A call with no argument will toggle the value.
|
|
||||||
--- @param auto boolean|nil
|
|
||||||
function hilbish:autoFlush(auto) end
|
|
||||||
|
|
||||||
--- Flush writes all buffered input to the sink.
|
|
||||||
function hilbish:flush() end
|
|
||||||
|
|
||||||
--- Reads a liine of input from the sink.
|
|
||||||
--- @returns string
|
|
||||||
function hilbish:read() end
|
|
||||||
|
|
||||||
--- Reads all input from the sink.
|
|
||||||
--- @returns string
|
|
||||||
function hilbish:readAll() end
|
|
||||||
|
|
||||||
--- Writes data to a sink.
|
|
||||||
function hilbish:write(str) end
|
|
||||||
|
|
||||||
--- Writes data to a sink with a newline at the end.
|
|
||||||
function hilbish:writeln(str) end
|
|
||||||
|
|
||||||
--- Starts running the job.
|
--- Starts running the job.
|
||||||
function hilbish.jobs:start() end
|
function hilbish.jobs:start() end
|
||||||
|
|
||||||
@ -200,10 +154,6 @@ function hilbish.jobs:stop() end
|
|||||||
--- It will throw if any error occurs.
|
--- It will throw if any error occurs.
|
||||||
function hilbish.module.load(path) end
|
function hilbish.module.load(path) end
|
||||||
|
|
||||||
--- Runs a command in Hilbish's shell script interpreter.
|
|
||||||
--- This is the equivalent of using `source`.
|
|
||||||
function hilbish.runner.sh(cmd) end
|
|
||||||
|
|
||||||
--- Starts a timer.
|
--- Starts a timer.
|
||||||
function hilbish.timers:start() end
|
function hilbish.timers:start() end
|
||||||
|
|
||||||
@ -262,4 +212,26 @@ function hilbish.timers.create(type, time, callback) end
|
|||||||
--- Retrieves a timer via its ID.
|
--- Retrieves a timer via its ID.
|
||||||
function hilbish.timers.get(id) end
|
function hilbish.timers.get(id) end
|
||||||
|
|
||||||
|
--- Sets/toggles the option of automatically flushing output.
|
||||||
|
--- A call with no argument will toggle the value.
|
||||||
|
--- @param auto boolean|nil
|
||||||
|
function hilbish:autoFlush(auto) end
|
||||||
|
|
||||||
|
--- Flush writes all buffered input to the sink.
|
||||||
|
function hilbish:flush() end
|
||||||
|
|
||||||
|
--- Reads a liine of input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function hilbish:read() end
|
||||||
|
|
||||||
|
--- Reads all input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function hilbish:readAll() end
|
||||||
|
|
||||||
|
--- Writes data to a sink.
|
||||||
|
function hilbish:write(str) end
|
||||||
|
|
||||||
|
--- Writes data to a sink with a newline at the end.
|
||||||
|
function hilbish:writeln(str) end
|
||||||
|
|
||||||
return hilbish
|
return hilbish
|
||||||
|
16
emmyLuaDocs/snail.lua
Normal file
16
emmyLuaDocs/snail.lua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
local snail = {}
|
||||||
|
|
||||||
|
--- Changes the directory of the snail instance.
|
||||||
|
--- The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
--- directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
function snail:dir(path) end
|
||||||
|
|
||||||
|
--- Creates a new Snail instance.
|
||||||
|
function snail.new() end
|
||||||
|
|
||||||
|
--- Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
function snail:run(command, streams) end
|
||||||
|
|
||||||
|
return snail
|
83
emmyLuaDocs/util.lua
Normal file
83
emmyLuaDocs/util.lua
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
local util = {}
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.AbbrevHome changes the user's home directory in the path string to ~ (tilde) end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.DoFile runs the contents of the file in the Lua runtime. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.DoString runs the code string in the Lua runtime. end
|
||||||
|
|
||||||
|
--- directory.
|
||||||
|
function util.ExpandHome expands ~ (tilde) in the path, changing it to the user home end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.ForEach loops through a Lua table. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
--- a string and a closure.
|
||||||
|
function util.HandleStrCallback handles function parameters for Go functions which take end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util.SetExports puts the Lua function exports in the table. end
|
||||||
|
|
||||||
|
--- It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||||
|
function util.SetField sets a field in a table, adding docs for it. end
|
||||||
|
|
||||||
|
--- is one which has a metatable proxy to ensure no overrides happen to it.
|
||||||
|
--- It sets the field in the table and sets the __docProp metatable on the
|
||||||
|
--- user facing table.
|
||||||
|
function util.SetFieldProtected sets a field in a protected table. A protected table end
|
||||||
|
|
||||||
|
--- Sets/toggles the option of automatically flushing output.
|
||||||
|
--- A call with no argument will toggle the value.
|
||||||
|
--- @param auto boolean|nil
|
||||||
|
function util:autoFlush(auto) end
|
||||||
|
|
||||||
|
--- Flush writes all buffered input to the sink.
|
||||||
|
function util:flush() end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
--- Reads a liine of input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function util:read() end
|
||||||
|
|
||||||
|
--- Reads all input from the sink.
|
||||||
|
--- @returns string
|
||||||
|
function util:readAll() end
|
||||||
|
|
||||||
|
--- Writes data to a sink.
|
||||||
|
function util:write(str) end
|
||||||
|
|
||||||
|
--- Writes data to a sink with a newline at the end.
|
||||||
|
function util:writeln(str) end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
---
|
||||||
|
function util. end
|
||||||
|
|
||||||
|
return util
|
534
exec.go
534
exec.go
@ -1,215 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os/exec"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hilbish/util"
|
|
||||||
|
|
||||||
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.NilValue
|
||||||
|
|
||||||
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)
|
runnerRun := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("run"))
|
||||||
hooks.Emit("command.preexec", input, cmdString)
|
_, err := rt.Call1(l.MainThread(), runnerRun, rt.StringValue(input), rt.BoolValue(priv))
|
||||||
|
|
||||||
rerun:
|
|
||||||
var exitCode uint8
|
|
||||||
var err error
|
|
||||||
var cont bool
|
|
||||||
var newline bool
|
|
||||||
// save incase it changes while prompting (For some reason)
|
|
||||||
currentRunner := runnerMode
|
|
||||||
if currentRunner.Type() == rt.StringType {
|
|
||||||
switch currentRunner.AsString() {
|
|
||||||
case "hybrid":
|
|
||||||
_, _, err = handleLua(input)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if cont {
|
|
||||||
input, err = continuePrompt(input, newline)
|
|
||||||
if err == nil {
|
|
||||||
goto rerun
|
|
||||||
} else if err == io.EOF {
|
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
if exErr, ok := isExecError(err); ok {
|
|
||||||
hooks.Emit("command." + exErr.typ, exErr.cmd)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmdFinish(exitCode, input, priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reprompt(input string, newline bool) (string, error) {
|
|
||||||
for {
|
|
||||||
/*
|
|
||||||
if strings.HasSuffix(input, "\\") {
|
|
||||||
input = strings.TrimSuffix(input, "\\") + "\n"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
in, err := continuePrompt(input, newline)
|
|
||||||
if err != nil {
|
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
|
||||||
return input, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, newline bool, runnerErr, err error) {
|
|
||||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
|
|
||||||
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 124, false, false, nil, err
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var runner *rt.Table
|
|
||||||
var ok bool
|
|
||||||
runnerRet := term.Get(0)
|
|
||||||
if runner, ok = runnerRet.TryTable(); !ok {
|
|
||||||
fmt.Fprintln(os.Stderr, "runner did not return a table")
|
|
||||||
exitCode = 125
|
|
||||||
input = userInput
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if code, ok := runner.Get(rt.StringValue("exitCode")).TryInt(); ok {
|
|
||||||
exitCode = uint8(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inp, ok := runner.Get(rt.StringValue("input")).TryString(); ok {
|
|
||||||
input = inp
|
|
||||||
}
|
|
||||||
|
|
||||||
if errStr, ok := runner.Get(rt.StringValue("err")).TryString(); ok {
|
|
||||||
runnerErr = fmt.Errorf("%s", errStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
|
|
||||||
continued = c
|
|
||||||
}
|
|
||||||
|
|
||||||
if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
|
|
||||||
newline = nl
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLua(input string) (string, uint8, error) {
|
func handleLua(input string) (string, uint8, error) {
|
||||||
@ -239,326 +50,13 @@ 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) {
|
|
||||||
_, _, err := execCommand(cmdString, nil)
|
|
||||||
if err != nil {
|
|
||||||
// If input is incomplete, start multiline prompting
|
|
||||||
if syntax.IsIncomplete(err) {
|
|
||||||
if !interactive {
|
|
||||||
return cmdString, 126, false, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newline := false
|
|
||||||
if strings.Contains(err.Error(), "unclosed here-document") {
|
|
||||||
newline = true
|
|
||||||
}
|
|
||||||
return cmdString, 126, true, newline, err
|
|
||||||
} else {
|
|
||||||
if code, ok := interp.IsExitStatus(err); ok {
|
|
||||||
return cmdString, code, false, false, nil
|
|
||||||
} else {
|
|
||||||
return cmdString, 126, false, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := 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 lookpath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
|
|
||||||
var skip []string
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
skip = []string{"./", "../", "~/", "C:"}
|
|
||||||
} else {
|
|
||||||
skip = []string{"./", "/", "../", "~/"}
|
|
||||||
}
|
|
||||||
for _, s := range skip {
|
|
||||||
if strings.HasPrefix(file, s) {
|
|
||||||
return file, findExecutable(file, false, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
|
||||||
path := filepath.Join(dir, file)
|
|
||||||
err := findExecutable(path, true, false)
|
|
||||||
if err == errNotExec {
|
|
||||||
return "", err
|
|
||||||
} else if err == nil {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", os.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitInput(input string) ([]string, string) {
|
func splitInput(input string) ([]string, string) {
|
||||||
// end my suffering
|
// end my suffering
|
||||||
// TODO: refactor this garbage
|
// TODO: refactor this garbage
|
||||||
quoted := false
|
quoted := false
|
||||||
startlastcmd := false
|
|
||||||
lastcmddone := false
|
|
||||||
cmdArgs := []string{}
|
cmdArgs := []string{}
|
||||||
sb := &strings.Builder{}
|
sb := &strings.Builder{}
|
||||||
cmdstr := &strings.Builder{}
|
cmdstr := &strings.Builder{}
|
||||||
lastcmd := "" //readline.GetHistory(readline.HistorySize() - 1)
|
|
||||||
|
|
||||||
for _, r := range input {
|
for _, r := range input {
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
@ -574,22 +72,6 @@ func splitInput(input string) ([]string, string) {
|
|||||||
// if not quoted and there's a space then add to cmdargs
|
// if not quoted and there's a space then add to cmdargs
|
||||||
cmdArgs = append(cmdArgs, sb.String())
|
cmdArgs = append(cmdArgs, sb.String())
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
} else if !quoted && r == '^' && startlastcmd && !lastcmddone {
|
|
||||||
// if ^ is found, isnt in quotes and is
|
|
||||||
// the second occurence of the character and is
|
|
||||||
// the first time "^^" has been used
|
|
||||||
cmdstr.WriteString(lastcmd)
|
|
||||||
sb.WriteString(lastcmd)
|
|
||||||
|
|
||||||
startlastcmd = !startlastcmd
|
|
||||||
lastcmddone = !lastcmddone
|
|
||||||
|
|
||||||
continue
|
|
||||||
} else if !quoted && r == '^' && !lastcmddone {
|
|
||||||
// if ^ is found, isnt in quotes and is the
|
|
||||||
// first time of starting "^^"
|
|
||||||
startlastcmd = !startlastcmd
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
}
|
}
|
||||||
@ -601,11 +83,3 @@ func splitInput(input string) ([]string, string) {
|
|||||||
|
|
||||||
return cmdArgs, cmdstr.String()
|
return cmdArgs, cmdstr.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdFinish(code uint8, cmdstr string, private bool) {
|
|
||||||
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)))
|
|
||||||
// using AsValue (to convert to lua type) on an interface which is an int
|
|
||||||
// results in it being unknown in lua .... ????
|
|
||||||
// so we allow the hook handler to take lua runtime Values
|
|
||||||
hooks.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private)
|
|
||||||
}
|
|
||||||
|
@ -19,38 +19,25 @@ 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"
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fs struct{
|
var Loader = packagelib.Loader{
|
||||||
runner *interp.Runner
|
Load: loaderFunc,
|
||||||
Loader packagelib.Loader
|
Name: "fs",
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(runner *interp.Runner) *fs {
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
f := &fs{
|
|
||||||
runner: runner,
|
|
||||||
}
|
|
||||||
f.Loader = packagelib.Loader{
|
|
||||||
Load: f.loaderFunc,
|
|
||||||
Name: "fs",
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
|
||||||
exports := map[string]util.LuaExport{
|
exports := map[string]util.LuaExport{
|
||||||
"cd": util.LuaExport{f.fcd, 1, false},
|
"cd": util.LuaExport{fcd, 1, false},
|
||||||
"mkdir": util.LuaExport{f.fmkdir, 2, false},
|
"mkdir": util.LuaExport{fmkdir, 2, false},
|
||||||
"stat": util.LuaExport{f.fstat, 1, false},
|
"stat": util.LuaExport{fstat, 1, false},
|
||||||
"readdir": util.LuaExport{f.freaddir, 1, false},
|
"readdir": util.LuaExport{freaddir, 1, false},
|
||||||
"abs": util.LuaExport{f.fabs, 1, false},
|
"abs": util.LuaExport{fabs, 1, false},
|
||||||
"basename": util.LuaExport{f.fbasename, 1, false},
|
"basename": util.LuaExport{fbasename, 1, false},
|
||||||
"dir": util.LuaExport{f.fdir, 1, false},
|
"dir": util.LuaExport{fdir, 1, false},
|
||||||
"glob": util.LuaExport{f.fglob, 1, false},
|
"glob": util.LuaExport{fglob, 1, false},
|
||||||
"join": util.LuaExport{f.fjoin, 0, true},
|
"join": util.LuaExport{fjoin, 0, true},
|
||||||
"pipe": util.LuaExport{f.fpipe, 0, false},
|
"pipe": util.LuaExport{fpipe, 0, false},
|
||||||
}
|
}
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
util.SetExports(rtm, mod, exports)
|
util.SetExports(rtm, mod, exports)
|
||||||
@ -65,7 +52,7 @@ func (f *fs) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
// This can be used to resolve short paths like `..` to `/home/user`.
|
// This can be used to resolve short paths like `..` to `/home/user`.
|
||||||
// #param path string
|
// #param path string
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
path, err := c.StringArg(0)
|
path, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,7 +72,7 @@ func (f *fs) fabs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// `.` will be returned.
|
// `.` will be returned.
|
||||||
// #param path string Path to get the base name of.
|
// #param path string Path to get the base name of.
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fbasename(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
|
||||||
}
|
}
|
||||||
@ -100,7 +87,7 @@ func (f *fs) fbasename(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// cd(dir)
|
// cd(dir)
|
||||||
// Changes Hilbish's directory to `dir`.
|
// Changes Hilbish's directory to `dir`.
|
||||||
// #param dir string Path to change directory to.
|
// #param dir string Path to change directory to.
|
||||||
func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fcd(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
|
||||||
}
|
}
|
||||||
@ -110,12 +97,10 @@ func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
path = util.ExpandHome(strings.TrimSpace(path))
|
path = util.ExpandHome(strings.TrimSpace(path))
|
||||||
|
|
||||||
abspath, _ := filepath.Abs(path)
|
|
||||||
err = os.Chdir(path)
|
err = os.Chdir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
interp.Dir(abspath)(f.runner)
|
|
||||||
|
|
||||||
return c.Next(), err
|
return c.Next(), err
|
||||||
}
|
}
|
||||||
@ -125,7 +110,7 @@ func (f *fs) fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// `~/Documents/doc.txt` then this function will return `~/Documents`.
|
// `~/Documents/doc.txt` then this function will return `~/Documents`.
|
||||||
// #param path string Path to get the directory for.
|
// #param path string Path to get the directory for.
|
||||||
// #returns string
|
// #returns string
|
||||||
func (f *fs) fdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fdir(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
|
||||||
}
|
}
|
||||||
@ -156,7 +141,7 @@ print(matches)
|
|||||||
-- -> {'init.lua', 'code.lua'}
|
-- -> {'init.lua', 'code.lua'}
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fglob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fglob(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
|
||||||
}
|
}
|
||||||
@ -190,7 +175,7 @@ print(fs.join(hilbish.userDir.config, 'hilbish'))
|
|||||||
-- -> '/home/user/.config/hilbish' on Linux
|
-- -> '/home/user/.config/hilbish' on Linux
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
strs := make([]string, len(c.Etc()))
|
strs := make([]string, len(c.Etc()))
|
||||||
for i, v := range c.Etc() {
|
for i, v := range c.Etc() {
|
||||||
if v.Type() != rt.StringType {
|
if v.Type() != rt.StringType {
|
||||||
@ -217,7 +202,7 @@ func (f *fs) fjoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
fs.mkdir('./foo/bar', true)
|
fs.mkdir('./foo/bar', true)
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.CheckNArgs(2); err != nil {
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -248,7 +233,7 @@ func (f *fs) fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// The type returned is a Lua file, same as returned from `io` functions.
|
// The type returned is a Lua file, same as returned from `io` functions.
|
||||||
// #returns File
|
// #returns File
|
||||||
// #returns File
|
// #returns File
|
||||||
func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
rf, wf, err := os.Pipe()
|
rf, wf, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -263,7 +248,7 @@ func (f *fs) fpipe(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// Returns a list of all files and directories in the provided path.
|
// Returns a list of all files and directories in the provided path.
|
||||||
// #param dir string
|
// #param dir string
|
||||||
// #returns table
|
// #returns table
|
||||||
func (f *fs) freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func freaddir(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
|
||||||
}
|
}
|
||||||
@ -311,7 +296,7 @@ Would print the following:
|
|||||||
]]--
|
]]--
|
||||||
#example
|
#example
|
||||||
*/
|
*/
|
||||||
func (f *fs) fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func fstat(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
|
||||||
}
|
}
|
||||||
|
221
golibs/snail/lua.go
Normal file
221
golibs/snail/lua.go
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// shell script interpreter library
|
||||||
|
/*
|
||||||
|
The snail library houses Hilbish's Lua wrapper of its shell script interpreter.
|
||||||
|
It's not very useful other than running shell scripts, which can be done with other
|
||||||
|
Hilbish functions.
|
||||||
|
*/
|
||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var snailMetaKey = rt.StringValue("hshsnail")
|
||||||
|
var Loader = packagelib.Loader{
|
||||||
|
Load: loaderFunc,
|
||||||
|
Name: "snail",
|
||||||
|
}
|
||||||
|
|
||||||
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
snailMeta := rt.NewTable()
|
||||||
|
snailMethods := rt.NewTable()
|
||||||
|
snailFuncs := map[string]util.LuaExport{
|
||||||
|
"run": {snailrun, 3, false},
|
||||||
|
"dir": {snaildir, 2, 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{snailnew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return rt.TableValue(mod), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// new() -> @Snail
|
||||||
|
// Creates a new Snail instance.
|
||||||
|
func snailnew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
s := New(t.Runtime)
|
||||||
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(snailUserData(s))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// run(command, streams)
|
||||||
|
// Runs a shell command. Works the same as `hilbish.run`, but only accepts a table of streams.
|
||||||
|
// #param command string
|
||||||
|
// #param streams table
|
||||||
|
func snailrun(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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 be a table")
|
||||||
|
}
|
||||||
|
|
||||||
|
var newline bool
|
||||||
|
var cont bool
|
||||||
|
var luaErr rt.Value = rt.NilValue
|
||||||
|
exitCode := 0
|
||||||
|
bg, _, _, err := s.Run(cmd, streams)
|
||||||
|
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 {
|
||||||
|
if exErr, ok := util.IsExecError(err); ok {
|
||||||
|
exitCode = exErr.Code
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// dir(path)
|
||||||
|
// Changes the directory of the snail instance.
|
||||||
|
// The interpreter keeps its set directory even when the Hilbish process changes
|
||||||
|
// directory, so this should be called on the `hilbish.cd` hook.
|
||||||
|
// #param path string Has to be an absolute path.
|
||||||
|
func snaildir(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
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
interp.Dir(dir)(s.runner)
|
||||||
|
return c.Next(), 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.(*util.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 {
|
||||||
|
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())
|
||||||
|
}
|
302
golibs/snail/snail.go
Normal file
302
golibs/snail/snail.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package snail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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
|
||||||
|
// A Snail is a shell script interpreter instance.
|
||||||
|
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.list()")
|
||||||
|
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()] = v.AsTable().Get(rt.StringValue("exec")).AsClosure()
|
||||||
|
})
|
||||||
|
if cmd := cmds[args[0]]; cmd != nil {
|
||||||
|
stdin := util.NewSinkInput(s.runtime, hc.Stdin)
|
||||||
|
stdout := util.NewSinkOutput(s.runtime, hc.Stdout)
|
||||||
|
stderr := util.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()
|
||||||
|
}
|
6
job.go
6
job.go
@ -56,8 +56,8 @@ func (j *job) start() error {
|
|||||||
}
|
}
|
||||||
j.setHandle(&cmd)
|
j.setHandle(&cmd)
|
||||||
}
|
}
|
||||||
// bgProcAttr is defined in execfile_<os>.go, it holds a procattr struct
|
// bgProcAttr is defined in job_<os>.go, it holds a procattr struct
|
||||||
// in a simple explanation, it makes signals from hilbish (sigint)
|
// in a simple explanation, it makes signals from hilbish (like sigint)
|
||||||
// not go to it (child process)
|
// not go to it (child process)
|
||||||
j.handle.SysProcAttr = bgProcAttr
|
j.handle.SysProcAttr = bgProcAttr
|
||||||
// reset output buffers
|
// reset output buffers
|
||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,10 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setpgid: true,
|
||||||
|
}
|
||||||
|
|
||||||
func (j *job) foreground() error {
|
func (j *job) foreground() error {
|
||||||
if jobs.foreground {
|
if jobs.foreground {
|
||||||
return errors.New("(another) job already foregrounded")
|
return errors.New("(another) job already foregrounded")
|
||||||
|
@ -4,8 +4,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||||
|
}
|
||||||
|
|
||||||
func (j *job) foreground() error {
|
func (j *job) foreground() error {
|
||||||
return errors.New("not supported on windows")
|
return errors.New("not supported on windows")
|
||||||
}
|
}
|
||||||
|
7
lua.go
7
lua.go
@ -9,6 +9,7 @@ import (
|
|||||||
"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"
|
||||||
@ -24,16 +25,14 @@ 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
|
||||||
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)
|
||||||
f := fs.New(runner)
|
|
||||||
lib.LoadLibs(l, f.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)
|
||||||
|
12
main.go
12
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 (
|
||||||
@ -38,7 +37,6 @@ var (
|
|||||||
cmds *commander.Commander
|
cmds *commander.Commander
|
||||||
defaultConfPath string
|
defaultConfPath string
|
||||||
defaultHistPath string
|
defaultHistPath string
|
||||||
runner *interp.Runner
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -58,7 +56,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runner, _ = interp.New()
|
|
||||||
curuser, _ = user.Current()
|
curuser, _ = user.Current()
|
||||||
confDir, _ = os.UserConfigDir()
|
confDir, _ = os.UserConfigDir()
|
||||||
|
|
||||||
@ -327,15 +324,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()
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ local commander = require 'commander'
|
|||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
local dirs = require 'nature.dirs'
|
local dirs = require 'nature.dirs'
|
||||||
|
|
||||||
dirs.old = hilbish.cwd()
|
|
||||||
commander.register('cd', function (args, sinks)
|
commander.register('cd', function (args, sinks)
|
||||||
|
local oldPath = hilbish.cwd()
|
||||||
|
|
||||||
if #args > 1 then
|
if #args > 1 then
|
||||||
sinks.out:writeln("cd: too many arguments")
|
sinks.out:writeln("cd: too many arguments")
|
||||||
return 1
|
return 1
|
||||||
@ -16,13 +17,13 @@ commander.register('cd', function (args, sinks)
|
|||||||
sinks.out:writeln(path)
|
sinks.out:writeln(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
dirs.setOld(hilbish.cwd())
|
local absPath = fs.abs(path)
|
||||||
dirs.push(path)
|
|
||||||
|
|
||||||
local ok, err = pcall(function() fs.cd(path) end)
|
local ok, err = pcall(function() fs.cd(path) end)
|
||||||
if not ok then
|
if not ok then
|
||||||
sinks.out:writeln(err)
|
sinks.out:writeln(err)
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
bait.throw('cd', path)
|
|
||||||
|
bait.throw('cd', path, oldPath)
|
||||||
|
bait.throw('hilbish.cd', absPath, oldPath)
|
||||||
end)
|
end)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
-- internal directory management
|
-- internal directory management
|
||||||
-- The dirs module defines a small set of functions to store and manage
|
-- The dirs module defines a small set of functions to store and manage
|
||||||
-- directories.
|
-- directories.
|
||||||
|
local bait = require 'bait'
|
||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
|
|
||||||
local dirs = {}
|
local dirs = {}
|
||||||
@ -47,11 +48,11 @@ end
|
|||||||
--- @param dir string
|
--- @param dir string
|
||||||
function dirs.push(dir)
|
function dirs.push(dir)
|
||||||
dirs.recentDirs[dirs.recentSize + 1] = nil
|
dirs.recentDirs[dirs.recentSize + 1] = nil
|
||||||
if dirs.recentDirs[#dirs.recentDirs - 1] ~= d then
|
if dirs.recentDirs[#dirs.recentDirs - 1] ~= dir then
|
||||||
ok, d = pcall(fs.abs, d)
|
local ok, dir = pcall(fs.abs, dir)
|
||||||
assert(ok, 'could not turn "' .. d .. '"into an absolute path')
|
assert(ok, 'could not turn "' .. dir .. '"into an absolute path')
|
||||||
|
|
||||||
table.insert(dirs.recentDirs, 1, d)
|
table.insert(dirs.recentDirs, 1, dir)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -77,4 +78,9 @@ function dirs.setOld(d)
|
|||||||
dirs.old = d
|
dirs.old = d
|
||||||
end
|
end
|
||||||
|
|
||||||
|
bait.catch('hilbish.cd', function(path, oldPath)
|
||||||
|
dirs.setOld(oldPath)
|
||||||
|
dirs.push(path)
|
||||||
|
end)
|
||||||
|
|
||||||
return dirs
|
return dirs
|
||||||
|
78
nature/hilbish.lua
Normal file
78
nature/hilbish.lua
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
-- @module hilbish
|
||||||
|
local bait = require 'bait'
|
||||||
|
local snail = require 'snail'
|
||||||
|
|
||||||
|
hilbish.snail = snail.new()
|
||||||
|
bait.catch('hilbish.cd', function(path)
|
||||||
|
hilbish.snail:dir(path)
|
||||||
|
end)
|
||||||
|
--- 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.
|
||||||
|
--- #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
|
||||||
|
-- @param cmd string
|
||||||
|
-- @param streams table|boolean
|
||||||
|
-- @returns number, string, string
|
||||||
|
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
|
||||||
|
|
||||||
|
--- Sets the execution/runner mode for interactive Hilbish.
|
||||||
|
--- **NOTE: This function is deprecated and will be removed in 3.0**
|
||||||
|
--- Use `hilbish.runner.setCurrent` instead.
|
||||||
|
--- This determines whether Hilbish wll try to run input as Lua
|
||||||
|
--- and/or sh or only do one of either.
|
||||||
|
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||||
|
--- sh, and lua. It also accepts a function, to which if it is passed one
|
||||||
|
--- will call it to execute user input instead.
|
||||||
|
--- Read [about runner mode](../features/runner-mode) for more information.
|
||||||
|
-- @param mode string|function
|
||||||
|
function hilbish.runnerMode(mode)
|
||||||
|
if type(mode) == 'string' then
|
||||||
|
hilbish.runner.setCurrent(mode)
|
||||||
|
elseif type(mode) == 'function' then
|
||||||
|
hilbish.runner.set('_', {
|
||||||
|
run = mode
|
||||||
|
})
|
||||||
|
hilbish.runner.setCurrent '_'
|
||||||
|
else
|
||||||
|
error('expected runner mode type to be either string or function, got', type(mode))
|
||||||
|
end
|
||||||
|
end
|
@ -18,6 +18,8 @@ table.insert(package.searchers, function(module)
|
|||||||
return function() return hilbish.module.load(path) end, path
|
return function() return hilbish.module.load(path) end, path
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
require 'nature.hilbish'
|
||||||
|
|
||||||
require 'nature.commands'
|
require 'nature.commands'
|
||||||
require 'nature.completions'
|
require 'nature.completions'
|
||||||
require 'nature.opts'
|
require 'nature.opts'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
-- @module hilbish.runner
|
-- @module hilbish.runner
|
||||||
|
local snail = require 'snail'
|
||||||
local currentRunner = 'hybrid'
|
local currentRunner = 'hybrid'
|
||||||
local runners = {}
|
local runners = {}
|
||||||
|
|
||||||
@ -71,10 +72,8 @@ end
|
|||||||
--- Sets Hilbish's runner mode by name.
|
--- Sets Hilbish's runner mode by name.
|
||||||
--- @param name string
|
--- @param name string
|
||||||
function hilbish.runner.setCurrent(name)
|
function hilbish.runner.setCurrent(name)
|
||||||
local r = hilbish.runner.get(name)
|
hilbish.runner.get(name) -- throws if it doesnt exist.
|
||||||
currentRunner = name
|
currentRunner = name
|
||||||
|
|
||||||
hilbish.runner.setMode(r.run)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the current runner by name.
|
--- Returns the current runner by name.
|
||||||
@ -83,6 +82,81 @@ function hilbish.runner.getCurrent()
|
|||||||
return currentRunner
|
return currentRunner
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- **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.
|
||||||
|
--- It takes a callback, which will be used to execute all interactive input.
|
||||||
|
--- Or a string which names the runner mode to use.
|
||||||
|
-- @param mode string|function
|
||||||
|
function hilbish.runner.setMode(mode)
|
||||||
|
hilbish.runnerMode(mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function finishExec(exitCode, input, priv)
|
||||||
|
hilbish.exitCode = exitCode
|
||||||
|
bait.throw('command.exit', exitCode, input, priv)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function continuePrompt(prev, newline)
|
||||||
|
local multilinePrompt = hilbish.multiprompt()
|
||||||
|
-- the return of hilbish.read is nil when error or ctrl-d
|
||||||
|
local cont = hilbish.read(multilinePrompt)
|
||||||
|
if not cont then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if newline then
|
||||||
|
cont = '\n' .. cont
|
||||||
|
end
|
||||||
|
|
||||||
|
if cont:match '\\$' then
|
||||||
|
cont = cont:gsub('\\$', '') .. '\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
return prev .. cont
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Runs `input` with the currently set Hilbish runner.
|
||||||
|
--- This method is how Hilbish executes commands.
|
||||||
|
--- `priv` is an optional boolean used to state if the input should be saved to history.
|
||||||
|
-- @param input string
|
||||||
|
-- @param priv bool
|
||||||
|
function hilbish.runner.run(input, priv)
|
||||||
|
local command = hilbish.aliases.resolve(input)
|
||||||
|
bait.throw('command.preexec', input, command)
|
||||||
|
|
||||||
|
::rerun::
|
||||||
|
local runner = hilbish.runner.get(currentRunner)
|
||||||
|
local ok, out = pcall(runner.run, input)
|
||||||
|
if not ok then
|
||||||
|
io.stderr:write(out .. '\n')
|
||||||
|
finishExec(124, out.input, priv)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if out.continue then
|
||||||
|
local contInput = continuePrompt(input, out.newline)
|
||||||
|
if contInput then
|
||||||
|
input = contInput
|
||||||
|
goto rerun
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if out.err then
|
||||||
|
local fields = string.split(out.err, ': ')
|
||||||
|
if fields[2] == 'not-found' or fields[2] == 'not-executable' then
|
||||||
|
bait.throw('command.' .. fields[2], fields[1])
|
||||||
|
else
|
||||||
|
io.stderr:write(out.err .. '\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
finishExec(out.exitCode, out.input, priv)
|
||||||
|
end
|
||||||
|
|
||||||
|
function hilbish.runner.sh(input)
|
||||||
|
return hilbish.snail:run(input)
|
||||||
|
end
|
||||||
|
|
||||||
hilbish.runner.add('hybrid', function(input)
|
hilbish.runner.add('hybrid', function(input)
|
||||||
local cmdStr = hilbish.aliases.resolve(input)
|
local cmdStr = hilbish.aliases.resolve(input)
|
||||||
|
|
||||||
@ -109,7 +183,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', hilbish.runner.sh)
|
||||||
return hilbish.runner.sh(input)
|
hilbish.runner.setCurrent 'hybrid'
|
||||||
end)
|
|
||||||
|
|
||||||
|
@ -53,9 +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},
|
|
||||||
"lua": {luaRunner, 1, false},
|
"lua": {luaRunner, 1, false},
|
||||||
"setMode": {hlrunnerMode, 1, false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
@ -64,44 +62,6 @@ func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
|||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
// #interface runner
|
|
||||||
// setMode(cb)
|
|
||||||
// This is the same as the `hilbish.runnerMode` function.
|
|
||||||
// It takes a callback, which will be used to execute all interactive input.
|
|
||||||
// In normal cases, neither callbacks should be overrided by the user,
|
|
||||||
// as the higher level functions listed below this will handle it.
|
|
||||||
// #param cb function
|
|
||||||
func _runnerMode() {}
|
|
||||||
|
|
||||||
// #interface runner
|
|
||||||
// sh(cmd)
|
|
||||||
// Runs a command in Hilbish's shell script interpreter.
|
|
||||||
// This is the equivalent of using `source`.
|
|
||||||
// #param cmd string
|
|
||||||
func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmd, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, exitCode, cont, newline, err := execSh(aliases.Resolve(cmd))
|
|
||||||
var luaErr rt.Value = rt.NilValue
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
|
|
||||||
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface runner
|
// #interface runner
|
||||||
// lua(cmd)
|
// lua(cmd)
|
||||||
// Evaluates `cmd` as Lua input. This is the same as using `dofile`
|
// Evaluates `cmd` as Lua input. This is the same as using `dofile`
|
||||||
|
@ -1,35 +1,32 @@
|
|||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hilbish/util"
|
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sinkMetaKey = rt.StringValue("hshsink")
|
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{
|
Rw *bufio.ReadWriter
|
||||||
writer *bufio.Writer
|
|
||||||
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 SinkLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
sinkMeta := rt.NewTable()
|
sinkMeta := rt.NewTable()
|
||||||
|
|
||||||
sinkMethods := rt.NewTable()
|
sinkMethods := rt.NewTable()
|
||||||
sinkFuncs := map[string]util.LuaExport{
|
sinkFuncs := map[string]LuaExport{
|
||||||
"flush": {luaSinkFlush, 1, false},
|
"flush": {luaSinkFlush, 1, false},
|
||||||
"read": {luaSinkRead, 1, false},
|
"read": {luaSinkRead, 1, false},
|
||||||
"readAll": {luaSinkReadAll, 1, false},
|
"readAll": {luaSinkReadAll, 1, false},
|
||||||
@ -37,7 +34,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)
|
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,10 +61,25 @@ 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))
|
||||||
|
|
||||||
|
exports := map[string]LuaExport{
|
||||||
|
"new": {luaSinkNew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
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
|
// #member
|
||||||
// readAll() -> string
|
// readAll() -> string
|
||||||
// --- @returns string
|
// --- @returns string
|
||||||
@ -84,7 +96,7 @@ func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
|
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
for {
|
for {
|
||||||
line, err := s.reader.ReadString('\n')
|
line, err := s.Rw.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
@ -113,7 +125,7 @@ func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
str, _ := s.reader.ReadString('\n')
|
str, _ := s.Rw.ReadString('\n')
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
|
||||||
}
|
}
|
||||||
@ -135,9 +147,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Write([]byte(data))
|
s.Rw.Write([]byte(data))
|
||||||
if s.autoFlush {
|
if s.autoFlush {
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -160,9 +172,9 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Write([]byte(data + "\n"))
|
s.Rw.Write([]byte(data + "\n"))
|
||||||
if s.autoFlush {
|
if s.autoFlush {
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -181,7 +193,7 @@ func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writer.Flush()
|
s.Rw.Flush()
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
@ -212,11 +224,24 @@ 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 NewSink(rtm *rt.Runtime, Rw io.ReadWriter) *Sink {
|
||||||
s := &sink{
|
s := &Sink{
|
||||||
reader: bufio.NewReader(r),
|
Rw: bufio.NewReadWriter(bufio.NewReader(Rw), bufio.NewWriter(Rw)),
|
||||||
}
|
}
|
||||||
s.ud = sinkUserData(s)
|
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{
|
||||||
|
Rw: bufio.NewReadWriter(bufio.NewReader(r), nil),
|
||||||
|
}
|
||||||
|
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 +250,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),
|
Rw: bufio.NewReadWriter(nil, 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 +269,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())
|
||||||
}
|
}
|
11
util/streams.go
Normal file
11
util/streams.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Streams struct {
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
Stdin io.Reader
|
||||||
|
}
|
137
util/util.go
137
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)
|
||||||
@ -141,3 +214,67 @@ func AbbrevHome(path string) string {
|
|||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LookPath(file string) (string, error) { // custom lookpath function so we know if a command is found *and* is executable
|
||||||
|
var skip []string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
skip = []string{"./", "../", "~/", "C:"}
|
||||||
|
} else {
|
||||||
|
skip = []string{"./", "/", "../", "~/"}
|
||||||
|
}
|
||||||
|
for _, s := range skip {
|
||||||
|
if strings.HasPrefix(file, s) {
|
||||||
|
return file, FindExecutable(file, false, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||||
|
path := filepath.Join(dir, file)
|
||||||
|
err := FindExecutable(path, true, false)
|
||||||
|
if err == ErrNotExec {
|
||||||
|
return "", err
|
||||||
|
} else if err == nil {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
//go:build unix
|
//go:build unix
|
||||||
|
|
||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
func FindExecutable(path string, inPath, dirs bool) error {
|
||||||
Setpgid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func findExecutable(path string, inPath, dirs bool) error {
|
|
||||||
f, err := os.Stat(path)
|
f, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -25,5 +20,5 @@ func findExecutable(path string, inPath, dirs bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
@ -1,18 +1,13 @@
|
|||||||
//go:build windows
|
//go:build windows
|
||||||
|
|
||||||
package main
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bgProcAttr *syscall.SysProcAttr = &syscall.SysProcAttr{
|
func FindExecutable(path string, inPath, dirs bool) error {
|
||||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
|
||||||
}
|
|
||||||
|
|
||||||
func findExecutable(path string, inPath, dirs bool) error {
|
|
||||||
nameExt := filepath.Ext(path)
|
nameExt := filepath.Ext(path)
|
||||||
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
|
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
|
||||||
if inPath {
|
if inPath {
|
||||||
@ -26,15 +21,15 @@ func findExecutable(path string, inPath, dirs bool) error {
|
|||||||
} else {
|
} else {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if contains(pathExts, nameExt) { return nil }
|
if Contains(pathExts, nameExt) { return nil }
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if contains(pathExts, nameExt) { return nil }
|
if Contains(pathExts, nameExt) { return nil }
|
||||||
return errNotExec
|
return ErrNotExec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user