Compare commits

...

2 Commits

Author SHA1 Message Date
TorchedSammy 802f444ba6 docs: [ci] generate new docs 2022-03-20 19:16:13 +00:00
TorchedSammy 86a15e6363
feat: add configurable runner mode (closes #110) 2022-03-20 15:15:44 -04:00
6 changed files with 209 additions and 21 deletions

33
api.go
View File

@ -26,6 +26,7 @@ var exports = map[string]lua.LGFunction {
"complete": hlcomplete,
"cwd": hlcwd,
"exec": hlexec,
"runnerMode": hlrunnerMode,
"goro": hlgoro,
"multiprompt": hlmlprompt,
"prependPath": hlprependPath,
@ -106,6 +107,10 @@ Check out the {blue}{bold}guide{reset} command to get started.
util.Document(L, hshcomp, "Completions interface for Hilbish.")
L.SetField(mod, "completion", hshcomp)
runnerModule := runnerModeLoader(L)
util.Document(L, runnerModule, "Runner/exec interface for Hilbish.")
L.SetField(mod, "runner", runnerModule)
jobs = newJobHandler()
L.Push(mod)
@ -172,7 +177,7 @@ func unsetVimMode() {
func hlrun(L *lua.LState) int {
var exitcode uint8
cmd := L.CheckString(1)
err := execCommand(cmd, true)
err := execCommand(cmd)
if code, ok := interp.IsExitStatus(err); ok {
exitcode = code
@ -466,3 +471,29 @@ func hlinputMode(L *lua.LState) int {
}
return 0
}
// 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.
func hlrunnerMode(L *lua.LState) int {
mode := L.CheckAny(1)
switch mode.Type() {
case lua.LTString:
switch mode.String() {
// no fallthrough doesnt work so eh
case "hybrid": fallthrough
case "hybridRev": fallthrough
case "lua": fallthrough
case "sh":
runnerMode = mode
default: L.RaiseError("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received %v", mode)
}
case lua.LTFunction: runnerMode = mode
default: L.RaiseError("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received %v", mode)
}
return 0
}

View File

@ -37,6 +37,12 @@ Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which s
run(cmd) > Runs `cmd` in Hilbish's sh interpreter.
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.
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
which(binName) > Searches for an executable called `binName` in the directories of $PATH

View File

@ -0,0 +1,33 @@
Hilbish is *unique,* when interactive it first attempts to run input as
Lua and then tries shell script. But if you're normal, you wouldn't
really be using Hilbish anyway but you'd also not want this
(or maybe want Lua only in some cases.)
The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`,
which determines how Hilbish will run user input. By default, this is
set to `hybrid` which is the previously mentioned behaviour of running Lua
first then going to shell script. If you want the reverse order, you can
set it to `hybridRev` and for isolated modes there is `sh` and `lua`
respectively.
You can also set it to a function, which will be called everytime Hilbish
needs to run interactive input. For example, you can set this to a simple
function to compile and evaluate Fennel, and now you can run Fennel.
You can even mix it with sh to make a hybrid mode with Lua replaced by
Fennel.
An example:
hilbish.runnerMode(function(input)
local ok = pcall(fennel.eval, input)
if ok then
return 0, nil
end
return hilbish.runner.sh(input)
end)
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the sh and Lua runner functions that Hilbish itself uses.
A runner function is expected to return 2 values: the exit code, and an error.
The exit code has to be a number, it will be 0 otherwise and the error can be
`nil` to indicate no error.

View File

@ -69,6 +69,13 @@ function hilbish.read(prompt) end
--- @param cmd string
function hilbish.run(cmd) 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.
function hilbish.runnerMode() end
--- Runs the `cb` function after `time` in milliseconds
--- @param cb function
--- @param time number

107
exec.go
View File

@ -24,13 +24,80 @@ import (
)
var errNotExec = errors.New("not executable")
var runnerMode lua.LValue = lua.LString("hybrid")
func runInput(input string, priv bool) {
running = true
cmdString := aliases.Resolve(input)
hooks.Em.Emit("command.preexec", input, cmdString)
if runnerMode.Type() == lua.LTString {
switch runnerMode.String() {
case "hybrid":
_, err := handleLua(cmdString)
if err == nil {
cmdFinish(0, cmdString, priv)
return
}
exitCode, err := handleSh(cmdString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
cmdFinish(exitCode, cmdString, priv)
case "hybridRev":
_, err := handleSh(cmdString)
if err == nil {
cmdFinish(0, cmdString, priv)
return
}
exitCode, err := handleLua(cmdString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
cmdFinish(exitCode, cmdString, priv)
case "lua":
exitCode, err := handleLua(cmdString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
cmdFinish(exitCode, cmdString, priv)
case "sh":
exitCode, err := handleSh(cmdString)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
cmdFinish(exitCode, cmdString, priv)
}
} else {
// can only be a string or function so
err := l.CallByParam(lua.P{
Fn: runnerMode,
NRet: 2,
Protect: true,
}, lua.LString(cmdString))
if err != nil {
fmt.Fprintln(os.Stderr, err)
cmdFinish(124, cmdString, priv)
return
}
luaexitcode := l.Get(-2) // first return value (makes sense right i love stacks)
runErr := l.Get(-1)
l.Pop(2)
var exitCode uint8
if code, ok := luaexitcode.(lua.LNumber); luaexitcode != lua.LNil && ok {
exitCode = uint8(code)
}
if runErr != lua.LNil {
fmt.Fprintln(os.Stderr, runErr)
}
cmdFinish(exitCode, cmdString, priv)
}
}
func handleLua(cmdString string) (uint8, error) {
// First try to load input, essentially compiling to bytecode
fn, err := l.LoadString(cmdString)
if err != nil && noexecute {
@ -41,7 +108,7 @@ func runInput(input string, priv bool) {
}
}
*/
return
return 125, err
}
// And if there's no syntax errors and -n isnt provided, run
if !noexecute {
@ -49,12 +116,14 @@ func runInput(input string, priv bool) {
err = l.PCall(0, lua.MultRet, nil)
}
if err == nil {
cmdFinish(0, cmdString, priv)
return
return 0, nil
}
// Last option: use sh interpreter
err = execCommand(cmdString, priv)
return 125, err
}
func handleSh(cmdString string) (uint8, error) {
err := execCommand(cmdString)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
@ -63,34 +132,31 @@ func runInput(input string, priv bool) {
if err != nil {
break
}
err = execCommand(cmdString, priv)
if syntax.IsIncomplete(err) || strings.HasSuffix(input, "\\") {
err = execCommand(cmdString)
if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") {
continue
} else if code, ok := interp.IsExitStatus(err); ok {
cmdFinish(code, cmdString, priv)
return code, nil
} else if err != nil {
cmdFinish(1, cmdString, priv)
fmt.Fprintln(os.Stderr, err)
return 126, err
} else {
cmdFinish(0, cmdString, priv)
return 0, nil
}
break
}
} else {
if code, ok := interp.IsExitStatus(err); ok {
cmdFinish(code, cmdString, priv)
return code, nil
} else {
cmdFinish(126, cmdString, priv)
fmt.Fprintln(os.Stderr, err)
return 126, err
}
}
} else {
cmdFinish(0, cmdString, priv)
}
}
return 0, nil
}
// Run command in sh interpreter
func execCommand(cmd string, priv bool) error {
func execCommand(cmd string) error {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil {
return err
@ -141,7 +207,6 @@ func execCommand(cmd string, priv bool) error {
exitcode = uint8(code)
}
cmdFinish(exitcode, argstring, priv)
return interp.NewExitStatus(exitcode)
}

46
runnermode.go 100644
View File

@ -0,0 +1,46 @@
package main
import (
"github.com/yuin/gopher-lua"
)
func runnerModeLoader(L *lua.LState) *lua.LTable {
exports := map[string]lua.LGFunction{
"sh": shRunner,
"lua": luaRunner,
"setMode": hlrunnerMode,
}
mod := L.SetFuncs(L.NewTable(), exports)
L.SetField(mod, "mode", runnerMode)
return mod
}
func shRunner(L *lua.LState) int {
cmd := L.CheckString(1)
exitCode, err := handleSh(cmd)
var luaErr lua.LValue = lua.LNil
if err != nil {
luaErr = lua.LString(err.Error())
}
L.Push(lua.LNumber(exitCode))
L.Push(luaErr)
return 2
}
func luaRunner(L *lua.LState) int {
cmd := L.CheckString(1)
exitCode, err := handleLua(cmd)
var luaErr lua.LValue = lua.LNil
if err != nil {
luaErr = lua.LString(err.Error())
}
L.Push(lua.LNumber(exitCode))
L.Push(luaErr)
return 2
}