Compare commits

..

No commits in common. "f2a2ac44d64ab6fb67e9515da7ffca449d95fe81" and "8821171ff50e100121388dc03a0bbd0fcdaf3fd8" have entirely different histories.

6 changed files with 210 additions and 235 deletions

199
api.go
View File

@ -4,14 +4,10 @@
package main package main
import ( import (
"fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"syscall"
"time"
"hilbish/util" "hilbish/util"
@ -21,18 +17,10 @@ import (
) )
var exports = map[string]lua.LGFunction { var exports = map[string]lua.LGFunction {
"alias": hlalias,
"appendPath": hlappendPath,
"cwd": hlcwd,
"exec": hlexec,
"flag": hlflag,
"multiprompt": hlmlprompt,
"prependPath": hlprependPath,
"prompt": hlprompt,
"interval": hlinterval,
"read": hlread,
"run": hlrun, "run": hlrun,
"timeout": hltimeout, "flag": hlflag,
"cwd": hlcwd,
"read": hlread,
} }
var greeting string var greeting string
@ -159,184 +147,3 @@ func hlread(L *lua.LState) int {
return 1 return 1
} }
/* prompt(str)
Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values.
`%d` - Current working directory
`%u` - Name of current user
`%h` - Hostname of device */
func hlprompt(L *lua.LState) int {
prompt = L.CheckString(1)
return 0
}
// multiprompt(str)
// Changes the continued line prompt to `str`
func hlmlprompt(L *lua.LState) int {
multilinePrompt = L.CheckString(1)
return 0
}
// alias(cmd, orig)
// Sets an alias of `orig` to `cmd`
func hlalias(L *lua.LState) int {
alias := L.CheckString(1)
source := L.CheckString(2)
aliases.Add(alias, source)
return 1
}
// appendPath(dir)
// Appends `dir` to $PATH
func hlappendPath(L *lua.LState) int {
// check if dir is a table or a string
arg := L.Get(1)
fmt.Println(arg.Type())
if arg.Type() == lua.LTTable {
arg.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
appendPath(v.String())
})
} else if arg.Type() == lua.LTString {
appendPath(arg.String())
} else {
L.RaiseError("bad argument to appendPath (expected string or table, got %v)", L.Get(1).Type().String())
}
return 0
}
func appendPath(dir string) {
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
pathenv := os.Getenv("PATH")
// if dir isnt already in $PATH, add it
if !strings.Contains(pathenv, dir) {
os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
}
}
// exec(cmd)
// Replaces running hilbish with `cmd`
func hlexec(L *lua.LState) int {
cmd := L.CheckString(1)
cmdArgs, _ := splitInput(cmd)
cmdPath, err := exec.LookPath(cmdArgs[0])
if err != nil {
fmt.Println(err)
// if we get here, cmdPath will be nothing
// therefore nothing will run
}
// syscall.Exec requires an absolute path to a binary
// path, args, string slice of environments
// TODO: alternative for windows
syscall.Exec(cmdPath, cmdArgs, os.Environ())
return 0 // random thought: does this ever return?
}
// goro(fn)
// Puts `fn` in a goroutine
func hlgoroutine(L *lua.LState) int {
fn := L.CheckFunction(1)
argnum := L.GetTop()
args := make([]lua.LValue, argnum)
for i := 1; i <= argnum; i++ {
args[i - 1] = L.Get(i)
}
// call fn
go func() {
L.CallByParam(lua.P{
Fn: fn,
NRet: 0,
Protect: true,
}, args...)
}()
return 0
}
// timeout(cb, time)
// Runs the `cb` function after `time` in milliseconds
func hltimeout(L *lua.LState) int {
cb := L.CheckFunction(1)
ms := L.CheckInt(2)
timeout := time.Duration(ms) * time.Millisecond
time.Sleep(timeout)
L.CallByParam(lua.P{
Fn: cb,
NRet: 0,
Protect: true,
})
return 0
}
// interval(cb, time)
// Runs the `cb` function every `time` milliseconds
func hlinterval(L *lua.LState) int {
intervalfunc := L.CheckFunction(1)
ms := L.CheckInt(2)
interval := time.Duration(ms) * time.Millisecond
ticker := time.NewTicker(interval)
stop := make(chan lua.LValue)
go func() {
for {
select {
case <-ticker.C:
if err := L.CallByParam(lua.P{
Fn: intervalfunc,
NRet: 0,
Protect: true,
}); err != nil {
fmt.Fprintln(os.Stderr, "Error in interval function:\n\n", err)
stop <- lua.LTrue // stop the interval
}
case <-stop:
ticker.Stop()
return
}
}
}()
L.Push(lua.LChannel(stop))
return 1
}
// complete(scope, cb)
// Registers a completion handler for `scope`.
// A `scope` is currently only expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of the entries to complete.
// Nested tables will be used as sub-completions.
func hlcomplete(L *lua.LState) int {
scope := L.CheckString(1)
cb := L.CheckFunction(2)
luaCompletions[scope] = cb
return 0
}
// prependPath(dir)
// Prepends `dir` to $PATH
func hlprependPath(L *lua.LState) int {
dir := L.CheckString(1)
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
pathenv := os.Getenv("PATH")
// if dir isnt already in $PATH, add in
if !strings.Contains(pathenv, dir) {
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
}
return 0
}

29
docs/global.txt 100644
View File

@ -0,0 +1,29 @@
alias(cmd, orig) > Sets an alias of `orig` to `cmd`
appendPath(dir) > Appends `dir` to $PATH
complete(scope, cb) > Registers a completion handler for `scope`.
A `scope` is currently only expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of the entries to complete.
Nested tables will be used as sub-completions.
exec(cmd) > Replaces running hilbish with `cmd`
goro(fn) > Puts `fn` in a goroutine
interval(cb, time) > Runs the `cb` function every `time` milliseconds
multiprompt(str) > Changes the continued line prompt to `str`
prependPath(dir) > Prepends `dir` to $PATH
prompt(str) > Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values.
`%d` - Current working directory
`%u` - Name of current user
`%h` - Hostname of device
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds

View File

@ -1,39 +1,10 @@
alias(cmd, orig) > Sets an alias of `orig` to `cmd`
appendPath(dir) > Appends `dir` to $PATH
complete(scope, cb) > Registers a completion handler for `scope`.
A `scope` is currently only expected to be `command.<cmd>`,
replacing <cmd> with the name of the command (for example `command.git`).
`cb` must be a function that returns a table of the entries to complete.
Nested tables will be used as sub-completions.
cwd() > Returns the current directory of the shell cwd() > Returns the current directory of the shell
exec(cmd) > Replaces running hilbish with `cmd`
flag(f) > Checks if the `f` flag has been passed to Hilbish. flag(f) > Checks if the `f` flag has been passed to Hilbish.
goro(fn) > Puts `fn` in a goroutine
interval(cb, time) > Runs the `cb` function every `time` milliseconds
multiprompt(str) > Changes the continued line prompt to `str`
prependPath(dir) > Prepends `dir` to $PATH
prompt(str) > Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values.
`%d` - Current working directory
`%u` - Name of current user
`%h` - Hostname of device
read(prompt) -> input? > Read input from the user, using Hilbish's line editor/input reader. read(prompt) -> input? > Read input from the user, using Hilbish's line editor/input reader.
This is a separate instance from the one Hilbish actually uses. This is a separate instance from the one Hilbish actually uses.
Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen) Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
run(cmd) > Runs `cmd` in Hilbish's sh interpreter. run(cmd) > Runs `cmd` in Hilbish's sh interpreter
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds

View File

@ -1,4 +1,3 @@
+ `signal.sigint` > Sent when Hilbish receives SIGINT (on Ctrl-C). + `signal.sigint` > Sent when Hilbish receives SIGINT (used to Ctrl-C).
+ `signal.resize` > Sent when the terminal is resized. + `signal.resize` > Sent when the terminal is resized.
+ `signal.sigusr1`
+ `signal.sigusr2`

173
lua.go
View File

@ -3,6 +3,10 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"strings"
"syscall"
"time"
"hilbish/golibs/bait" "hilbish/golibs/bait"
"hilbish/golibs/commander" "hilbish/golibs/commander"
@ -10,6 +14,7 @@ import (
"hilbish/golibs/terminal" "hilbish/golibs/terminal"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
"layeh.com/gopher-luar"
) )
var minimalconf = `prompt '& '` var minimalconf = `prompt '& '`
@ -18,6 +23,16 @@ func luaInit() {
l = lua.NewState() l = lua.NewState()
l.OpenLibs() l.OpenLibs()
l.SetGlobal("prompt", l.NewFunction(hshprompt))
l.SetGlobal("multiprompt", l.NewFunction(hshmlprompt))
l.SetGlobal("alias", l.NewFunction(hshalias))
l.SetGlobal("appendPath", l.NewFunction(hshappendPath))
l.SetGlobal("prependPath", l.NewFunction(hshprependPath))
l.SetGlobal("exec", l.NewFunction(hshexec))
l.SetGlobal("goro", luar.New(l, hshgoroutine))
l.SetGlobal("timeout", luar.New(l, hshtimeout))
l.SetGlobal("interval", l.NewFunction(hshinterval))
// yes this is stupid, i know // yes this is stupid, i know
l.PreloadModule("hilbish", hilbishLoader) l.PreloadModule("hilbish", hilbishLoader)
l.DoString("hilbish = require 'hilbish'") l.DoString("hilbish = require 'hilbish'")
@ -46,7 +61,7 @@ func luaInit() {
} }
}) })
l.SetGlobal("complete", l.NewFunction(hlcomplete)) l.SetGlobal("complete", l.NewFunction(hshcomplete))
// Add more paths that Lua can require from // Add more paths that Lua can require from
l.DoString("package.path = package.path .. " + requirePaths) l.DoString("package.path = package.path .. " + requirePaths)
@ -87,3 +102,159 @@ func runLogin() {
} }
} }
/* prompt(str)
Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values.
`%d` - Current working directory
`%u` - Name of current user
`%h` - Hostname of device */
func hshprompt(L *lua.LState) int {
prompt = L.CheckString(1)
return 0
}
// multiprompt(str)
// Changes the continued line prompt to `str`
func hshmlprompt(L *lua.LState) int {
multilinePrompt = L.CheckString(1)
return 0
}
// alias(cmd, orig)
// Sets an alias of `orig` to `cmd`
func hshalias(L *lua.LState) int {
alias := L.CheckString(1)
source := L.CheckString(2)
aliases.Add(alias, source)
return 1
}
// appendPath(dir)
// Appends `dir` to $PATH
func hshappendPath(L *lua.LState) int {
// check if dir is a table or a string
arg := L.Get(1)
fmt.Println(arg.Type())
if arg.Type() == lua.LTTable {
arg.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
appendPath(v.String())
})
} else if arg.Type() == lua.LTString {
appendPath(arg.String())
} else {
L.RaiseError("bad argument to appendPath (expected string or table, got %v)", L.Get(1).Type().String())
}
return 0
}
func appendPath(dir string) {
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
pathenv := os.Getenv("PATH")
// if dir isnt already in $PATH, add it
if !strings.Contains(pathenv, dir) {
os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
}
}
// exec(cmd)
// Replaces running hilbish with `cmd`
func hshexec(L *lua.LState) int {
cmd := L.CheckString(1)
cmdArgs, _ := splitInput(cmd)
cmdPath, err := exec.LookPath(cmdArgs[0])
if err != nil {
fmt.Println(err)
// if we get here, cmdPath will be nothing
// therefore nothing will run
}
// syscall.Exec requires an absolute path to a binary
// path, args, string slice of environments
// TODO: alternative for windows
syscall.Exec(cmdPath, cmdArgs, os.Environ())
return 0 // random thought: does this ever return?
}
// goro(fn)
// Puts `fn` in a goroutine
func hshgoroutine(gofunc func()) {
go gofunc()
}
// timeout(cb, time)
// Runs the `cb` function after `time` in milliseconds
func hshtimeout(timeoutfunc func(), ms int) {
timeout := time.Duration(ms) * time.Millisecond
time.Sleep(timeout)
timeoutfunc()
}
// interval(cb, time)
// Runs the `cb` function every `time` milliseconds
func hshinterval(L *lua.LState) int {
intervalfunc := L.CheckFunction(1)
ms := L.CheckInt(2)
interval := time.Duration(ms) * time.Millisecond
ticker := time.NewTicker(interval)
stop := make(chan lua.LValue)
go func() {
for {
select {
case <-ticker.C:
if err := L.CallByParam(lua.P{
Fn: intervalfunc,
NRet: 0,
Protect: true,
}); err != nil {
fmt.Fprintln(os.Stderr, "Error in interval function:\n\n", err)
stop <- lua.LTrue // stop the interval
}
case <-stop:
ticker.Stop()
return
}
}
}()
L.Push(lua.LChannel(stop))
return 1
}
// complete(scope, cb)
// Registers a completion handler for `scope`.
// A `scope` is currently only expected to be `command.<cmd>`,
// replacing <cmd> with the name of the command (for example `command.git`).
// `cb` must be a function that returns a table of the entries to complete.
// Nested tables will be used as sub-completions.
func hshcomplete(L *lua.LState) int {
scope := L.CheckString(1)
cb := L.CheckFunction(2)
luaCompletions[scope] = cb
return 0
}
// prependPath(dir)
// Prepends `dir` to $PATH
func hshprependPath(L *lua.LState) int {
dir := L.CheckString(1)
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
pathenv := os.Getenv("PATH")
// if dir isnt already in $PATH, add in
if !strings.Contains(pathenv, dir) {
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
}
return 0
}

View File

@ -260,22 +260,20 @@ func fmtPrompt() string {
func handleSignals() { func handleSignals() {
c := make(chan os.Signal) c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGWINCH, syscall.SIGUSR1, syscall.SIGUSR2) signal.Notify(c, os.Interrupt, syscall.SIGWINCH)
for s := range c { for s := range c {
switch s { switch s {
case os.Interrupt: case os.Interrupt:
hooks.Em.Emit("signal.sigint") hooks.Em.Emit("signals.sigint")
if !running && interactive { if !running && interactive {
lr.ClearInput() lr.ClearInput()
} }
case syscall.SIGWINCH: case syscall.SIGWINCH:
hooks.Em.Emit("signal.resize") hooks.Em.Emit("signals.resize")
if !running && interactive { if !running && interactive {
lr.Resize() lr.Resize()
} }
case syscall.SIGUSR1: hooks.Em.Emit("signal.sigusr1")
case syscall.SIGUSR2: hooks.Em.Emit("signal.sigusr2")
} }
} }
} }