Compare commits

...

12 Commits

Author SHA1 Message Date
TorchedSammy 4e244e141f
fix: remove variables heading if there is none, account for global in new module doc format 2021-11-22 19:20:57 -05:00
TorchedSammy f741c2709c
fix: check and print module isnt found instead of failing 2021-11-22 19:20:31 -05:00
TorchedSammy 179947cd98
fix: only add __docProp metatable from SetField function 2021-11-22 19:19:36 -05:00
TorchedSammy 43ddab699f
feat: add in shell documentation for module properties 2021-11-22 18:59:28 -05:00
TorchedSammy 538ba3547f
chore: reorder code 2021-11-22 18:58:30 -05:00
TorchedSammy e67c549714
fix: get function docs for normal modules (regression from 52a6eb2125) 2021-11-22 18:29:26 -05:00
TorchedSammy 812de48558
fix: completions not working with aliases commands (resolves #82) 2021-11-22 18:01:59 -05:00
TorchedSammy 52a6eb2125
docs: document available bait hooks 2021-11-22 16:36:32 -05:00
TorchedSammy c61b428d67
fix: panic when error occurs in commander
check if commander has an error before trying to get a return code
2021-11-22 15:40:56 -05:00
TorchedSammy af4c3885ae
feat: add signal hooks for sigint and sigwinch (#80) 2021-11-22 15:13:05 -05:00
TorchedSammy 94a8b4ad47
feat: add prependPath function (resolves #81) 2021-11-22 12:46:39 -05:00
TorchedSammy 72cf776882
fix: use golang path list separator to not be unix specific 2021-11-22 12:45:45 -05:00
11 changed files with 168 additions and 38 deletions

View File

@ -0,0 +1,7 @@
+ `command.exit` -> code, cmdStr > Thrown when a command exits.
`code` is the exit code of the command, and `cmdStr` is the command that was run.
+ `command.not-found` -> cmdStr > Thrown when a command is not found.
+ `command.no-perm` -> cmdStr > Thrown when Hilbish attempts to execute a file but
has no permission.

View File

@ -0,0 +1,8 @@
Here is listed all scopes for bait hooks. If a hook is related to a command,
it will have the `command` scope, as example.
Here is the format for a doc for a hook:
+ <hook name> -> <args> > <description>
`<args>` just means the arguments of the hook. If a hook doc has the format
of `arg...`, it means the hook can take/recieve any number of `arg`.

View File

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

View File

@ -20,15 +20,19 @@ func New() Bait {
func (b *Bait) Loader(L *lua.LState) int { func (b *Bait) Loader(L *lua.LState) int {
mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{}) mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{})
util.Document(L, mod,
`Bait is the event emitter for Hilbish. Why name it bait?
Because it throws hooks that you can catch (emits events
that you can listen to) and because why not, fun naming
is fun. This is what you will use if you want to listen
in on hooks to know when certain things have happened,
like when you've changed directory, a command has
failed, etc. To find all available hooks, see doc hooks.`)
L.SetField(mod, "throw", luar.New(L, b.bthrow)) L.SetField(mod, "throw", luar.New(L, b.bthrow))
L.SetField(mod, "catch", luar.New(L, b.bcatch)) L.SetField(mod, "catch", luar.New(L, b.bcatch))
util.Document(L, mod, `Bait is the event emitter for Hilbish. Why name it bait?
Because it throws hooks that you can catch
(emits events that you can listen to) and because why not,
fun naming is fun. This is what you will use if you want to
listen in on hooks to know when certain things have happened,
like when you've changed directory, a command has failed, etc.`)
L.Push(mod) L.Push(mod)
return 1 return 1

View File

@ -32,13 +32,13 @@ func HilbishLoader(L *lua.LState) int {
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
} }
L.SetField(mod, "ver", lua.LString(version)) util.SetField(L, mod, "ver", lua.LString(version), "Hilbish version")
L.SetField(mod, "user", lua.LString(username)) util.SetField(L, mod, "user", lua.LString(username), "Username of user")
L.SetField(mod, "host", lua.LString(host)) util.SetField(L, mod, "host", lua.LString(host), "Host name of the machine")
L.SetField(mod, "home", lua.LString(homedir)) util.SetField(L, mod, "home", lua.LString(homedir), "Home directory of the user")
L.SetField(mod, "dataDir", lua.LString(dataDir)) util.SetField(L, mod, "dataDir", lua.LString(dataDir), "Directory for Hilbish's data files")
L.SetField(mod, "interactive", lua.LBool(interactive)) util.SetField(L, mod, "interactive", lua.LBool(interactive), "If this is an interactive shell")
L.SetField(mod, "login", lua.LBool(interactive)) util.SetField(L, mod, "login", lua.LBool(interactive), "Whether this is a login shell")
xdg := L.NewTable() xdg := L.NewTable()
L.SetField(xdg, "config", lua.LString(confDir)) L.SetField(xdg, "config", lua.LString(confDir))

25
lua.go
View File

@ -32,6 +32,7 @@ func LuaInit() {
l.SetGlobal("multiprompt", l.NewFunction(hshmlprompt)) l.SetGlobal("multiprompt", l.NewFunction(hshmlprompt))
l.SetGlobal("alias", l.NewFunction(hshalias)) l.SetGlobal("alias", l.NewFunction(hshalias))
l.SetGlobal("appendPath", l.NewFunction(hshappendPath)) l.SetGlobal("appendPath", l.NewFunction(hshappendPath))
l.SetGlobal("prependPath", l.NewFunction(hshprependPath))
l.SetGlobal("exec", l.NewFunction(hshexec)) l.SetGlobal("exec", l.NewFunction(hshexec))
l.SetGlobal("goro", luar.New(l, hshgoroutine)) l.SetGlobal("goro", luar.New(l, hshgoroutine))
l.SetGlobal("timeout", luar.New(l, hshtimeout)) l.SetGlobal("timeout", luar.New(l, hshtimeout))
@ -58,6 +59,13 @@ func LuaInit() {
hooks = bait.New() hooks = bait.New()
l.PreloadModule("bait", hooks.Loader) l.PreloadModule("bait", hooks.Loader)
// Add Ctrl-C handler
hooks.Em.On("signal.sigint", func() {
if !interactive {
os.Exit(0)
}
})
l.SetGlobal("complete", l.NewFunction(hshcomplete)) l.SetGlobal("complete", l.NewFunction(hshcomplete))
// Add more paths that Lua can require from // Add more paths that Lua can require from
@ -140,7 +148,7 @@ func hshappendPath(L *lua.LState) int {
// if dir isnt already in $PATH, add it // if dir isnt already in $PATH, add it
if !strings.Contains(pathenv, dir) { if !strings.Contains(pathenv, dir) {
os.Setenv("PATH", pathenv + ":" + dir) os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
} }
return 0 return 0
@ -226,3 +234,18 @@ func hshcomplete(L *lua.LState) int {
return 0 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

@ -265,13 +265,12 @@ func HandleSignals() {
for s := range c { for s := range c {
switch s { switch s {
case os.Interrupt: case os.Interrupt:
hooks.Em.Emit("signals.sigint")
if !running { if !running {
if !interactive {
os.Exit(0)
}
lr.ClearInput() lr.ClearInput()
} }
case syscall.SIGWINCH: case syscall.SIGWINCH:
hooks.Em.Emit("signals.resize")
if !running { if !running {
lr.Resize() lr.Resize()
} }

View File

@ -49,41 +49,90 @@ commander.register('doc', function(args)
local moddocPath = hilbish.dataDir .. '/docs/' local moddocPath = hilbish.dataDir .. '/docs/'
local globalDesc = [[ local globalDesc = [[
These are the global Hilbish functions that are always available and not part of a module.]] These are the global Hilbish functions that are always available and not part of a module.]]
local modDocFormat = [[
%s
%s
# Functions
]]
if #args > 0 then if #args > 0 then
local mod = table.concat(args, ' '):gsub('^%s*(.-)%s*$', '%1') local mod = args[1]
local f = io.open(moddocPath .. mod .. '.txt', 'rb') local f = io.open(moddocPath .. mod .. '.txt', 'rb')
local funcdocs = nil
if not f then if not f then
print('Could not find docs for module named ' .. mod .. '.') -- assume subdir
return 1 -- dataDir/docs/<mod>/<mod>.txt
moddocPath = moddocPath .. mod .. '/'
local subdocName = args[2]
if not subdocName then
subdocName = 'index'
end
f = io.open(moddocPath .. subdocName .. '.txt', 'rb')
if not f then
print('No documentation found for ' .. mod .. '.')
return
end
funcdocs = f:read '*a'
local subdocs = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.txt', '')))
end)
if subdocName == 'index' then
funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ')
end
end end
local desc = (mod == 'global' and globalDesc or getmetatable(require(mod)).__doc) if not funcdocs then
local funcdocs = f:read '*a' funcdocs = f:read '*a'
end
local desc = ''
local ok = pcall(require, mod)
local backtickOccurence = 0 local backtickOccurence = 0
print(desc .. '\n\n' .. lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function() local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function()
backtickOccurence = backtickOccurence + 1 backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then if backtickOccurence % 2 == 0 then
return '{reset}' return '{reset}'
else else
return '{underline}{green}' return '{underline}{green}'
end end
end))) end))
if mod == 'global' or ok then
local props = {}
local propstr = ''
local modDesc = globalDesc
if ok then
local modmt = getmetatable(require(mod))
modDesc = modmt.__doc
if modmt.__docProp then
-- not all modules have docs for properties
props = table.map(modmt.__docProp, function(v, k)
return lunacolors.underline(lunacolors.blue(k)) .. ' > ' .. v
end)
end
end
if #props > 0 then
propstr = '\n# Properties\n' .. table.concat(props, '\n') .. '\n'
end
desc = string.format(modDocFormat, modDesc, propstr)
end
print(desc .. formattedFuncs)
f:close() f:close()
return return
end end
local modules = table.map(fs.readdir(moddocPath), function(f) local modules = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(f:sub(1, -5))) return lunacolors.underline(lunacolors.blue(string.gsub(f, '.txt', '')))
end) end)
io.write [[ io.write [[
Welcome to Hilbish's doc tool! Here you can find documentation for builtin Welcome to Hilbish's doc tool! Here you can find documentation for builtin
functions and other things. functions and other things.
Usage: doc <module> Usage: doc <section> [subdoc]
A section is a module or a literal section and a subdoc is a subsection for it.
Available modules: ]] Available sections: ]]
print(table.concat(modules, ', ')) print(table.concat(modules, ', '))

18
rl.go
View File

@ -8,9 +8,9 @@ package main
// this is normal readline // this is normal readline
import ( import (
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"os"
"github.com/Rosettea/readline" "github.com/Rosettea/readline"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
@ -25,12 +25,24 @@ func NewLineReader(prompt string) *LineReader {
var completions []string var completions []string
// trim whitespace from ctx // trim whitespace from ctx
ctx = strings.TrimLeft(ctx, " ") ctx = strings.TrimLeft(ctx, " ")
fields := strings.Split(ctx, " ") fields, ctx := splitInput(ctx)
if len(fields) == 0 { if len(fields) == 0 {
return nil return nil
} }
for aliases[fields[0]] != "" {
alias := aliases[fields[0]]
ctx = alias + strings.TrimPrefix(ctx, fields[0])
fields = strings.Split(ctx, " ")
if aliases[fields[0]] == alias {
break
}
if aliases[fields[0]] != "" {
continue
}
}
if len(fields) == 1 { if len(fields) == 1 {
prefixes := []string{"./", "../"} prefixes := []string{"./", "../"}
for _, prefix := range prefixes { for _, prefix := range prefixes {

View File

@ -124,6 +124,13 @@ func execCommand(cmd string) error {
NRet: 1, NRet: 1,
Protect: true, Protect: true,
}, luacmdArgs) }, luacmdArgs)
if err != nil {
fmt.Fprintln(os.Stderr,
"Error in command:\n\n" + err.Error())
return interp.NewExitStatus(1)
}
luaexitcode := l.Get(-1) luaexitcode := l.Get(-1)
var exitcode uint8 = 0 var exitcode uint8 = 0
@ -133,10 +140,6 @@ func execCommand(cmd string) error {
exitcode = uint8(code) exitcode = uint8(code)
} }
if err != nil {
fmt.Fprintln(os.Stderr,
"Error in command:\n\n" + err.Error())
}
cmdFinish(exitcode, argstring) cmdFinish(exitcode, argstring)
return interp.NewExitStatus(exitcode) return interp.NewExitStatus(exitcode)
} }

View File

@ -2,9 +2,31 @@ package util
import "github.com/yuin/gopher-lua" import "github.com/yuin/gopher-lua"
// Document adds a documentation string to a module.
// It is accessible via the __doc metatable.
func Document(L *lua.LState, module lua.LValue, doc string) { func Document(L *lua.LState, module lua.LValue, doc string) {
mt := L.NewTable() mt := L.GetMetatable(module)
if mt == lua.LNil {
mt = L.NewTable()
L.SetMetatable(module, mt)
}
L.SetField(mt, "__doc", lua.LString(doc)) L.SetField(mt, "__doc", lua.LString(doc))
}
// 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.
func SetField(L *lua.LState, module lua.LValue, field string, value lua.LValue, doc string) {
mt := L.GetMetatable(module)
if mt == lua.LNil {
mt = L.NewTable()
docProp := L.NewTable()
L.SetField(mt, "__docProp", docProp)
L.SetMetatable(module, mt) L.SetMetatable(module, mt)
} }
docProp := L.GetTable(mt, lua.LString("__docProp"))
L.SetField(docProp, field, lua.LString(doc))
L.SetField(module, field, value)
}