diff --git a/api.go b/api.go index 291c9b2..1e85036 100644 --- a/api.go +++ b/api.go @@ -4,10 +4,14 @@ package main import ( + "fmt" "os" + "os/exec" "path/filepath" "runtime" "strings" + "syscall" + "time" "hilbish/util" @@ -17,10 +21,18 @@ import ( ) var exports = map[string]lua.LGFunction { - "run": hlrun, - "flag": hlflag, + "alias": hlalias, + "appendPath": hlappendPath, "cwd": hlcwd, + "exec": hlexec, + "flag": hlflag, + "multiprompt": hlmlprompt, + "prependPath": hlprependPath, + "prompt": hlprompt, + "interval": hlinterval, "read": hlread, + "run": hlrun, + "timeout": hltimeout, } var greeting string @@ -147,3 +159,184 @@ func hlread(L *lua.LState) int { 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.`, +// replacing 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 +} diff --git a/docs/global.txt b/docs/global.txt deleted file mode 100644 index 62d628b..0000000 --- a/docs/global.txt +++ /dev/null @@ -1,29 +0,0 @@ -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.`, -replacing 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 - diff --git a/docs/hilbish.txt b/docs/hilbish.txt index b74c69c..59f0a25 100644 --- a/docs/hilbish.txt +++ b/docs/hilbish.txt @@ -1,10 +1,39 @@ +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.`, +replacing 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 +exec(cmd) > Replaces running hilbish with `cmd` + 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. 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) -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 diff --git a/lua.go b/lua.go index 8ce4b9f..031ee51 100644 --- a/lua.go +++ b/lua.go @@ -3,10 +3,6 @@ package main import ( "fmt" "os" - "os/exec" - "strings" - "syscall" - "time" "hilbish/golibs/bait" "hilbish/golibs/commander" @@ -14,7 +10,6 @@ import ( "hilbish/golibs/terminal" "github.com/yuin/gopher-lua" - "layeh.com/gopher-luar" ) var minimalconf = `prompt '& '` @@ -23,16 +18,6 @@ func luaInit() { l = lua.NewState() 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 l.PreloadModule("hilbish", hilbishLoader) l.DoString("hilbish = require 'hilbish'") @@ -61,7 +46,7 @@ func luaInit() { } }) - l.SetGlobal("complete", l.NewFunction(hshcomplete)) + l.SetGlobal("complete", l.NewFunction(hlcomplete)) // Add more paths that Lua can require from l.DoString("package.path = package.path .. " + requirePaths) @@ -102,159 +87,3 @@ 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.`, -// replacing 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 -}