Compare commits

..

No commits in common. "65435572d4d329997931401b4a627061ccadcd17" and "123f8992b1b258bd649ea351ecb25ea9afc43dff" have entirely different histories.

12 changed files with 38 additions and 204 deletions

View File

@ -2,41 +2,6 @@
This is the changelog for the Hilbish shell made in Go and Lua. This is the changelog for the Hilbish shell made in Go and Lua.
## [0.7.0] - 2021-11-22
### Added
- `hilbish.interactive` and `hilbish.login` properties to figure out if Hilbish is interactive or a login shell, respectively.
- `hilbish.read` function to take input more elegantly than Lua's `io.read`
- Tab Completion Enhancements
- A new tab complete API has been added. It is the single `complete` function which takes a "scope" (example: `command.<cmdname>`) and a callback which is
expected to return a table. Users can now add custom completions for specific commands.
An example is:
```lua
complete('command.git', function()
return {
'add',
'version',
commit = {
'--message',
'--verbose',
'<file>'
}
}
end)
```
For `git`, Hilbish will complete commands add, version and commit. For the commit subcommand, it will complete the flags and/or files which `<file>` is used to represent.
- Hilbish will now complete binaries in $PATH, or any executable to a path (like `./` or `../`)
- Files with spaces will be automatically put in quotes and completions will work for them now.
- `prependPath` function (#81)
- Signal hooks (#80)
- This allows scripts to add their own way of handling terminal resizes (if you'd need that) or Ctrl-C
- Module properties (like `hilbish.ver`) are documented with the `doc` command.
- Document bait hooks
### Fixed
- The prompt won't come up on terminal resize anymore.
- `appendPath` should work properly on Windows.
- A panic when a commander has an error has been fixed.
## [0.6.1] - 2021-10-21 ## [0.6.1] - 2021-10-21
### Fixed ### Fixed
- Require paths now use the `dataDir` variable so there is no need to change it anymore unless you want to add more paths - Require paths now use the `dataDir` variable so there is no need to change it anymore unless you want to add more paths
@ -284,7 +249,6 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. First "stable" release of Hilbish.
[0.7.0]: https://github.com/Rosettea/Hilbish/compare/v0.6.1...v0.7.0
[0.6.1]: https://github.com/Rosettea/Hilbish/compare/v0.6.0...v0.6.1 [0.6.1]: https://github.com/Rosettea/Hilbish/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/Rosettea/Hilbish/compare/v0.5.1...v0.6.0 [0.6.0]: https://github.com/Rosettea/Hilbish/compare/v0.5.1...v0.6.0
[0.5.1]: https://github.com/Rosettea/Hilbish/compare/v0.5.0...v0.5.1 [0.5.1]: https://github.com/Rosettea/Hilbish/compare/v0.5.0...v0.5.1

View File

@ -1,7 +0,0 @@
+ `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

@ -1,8 +0,0 @@
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

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

View File

@ -20,19 +20,15 @@ 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
} }
util.SetField(L, mod, "ver", lua.LString(version), "Hilbish version") L.SetField(mod, "ver", lua.LString(version))
util.SetField(L, mod, "user", lua.LString(username), "Username of user") L.SetField(mod, "user", lua.LString(username))
util.SetField(L, mod, "host", lua.LString(host), "Host name of the machine") L.SetField(mod, "host", lua.LString(host))
util.SetField(L, mod, "home", lua.LString(homedir), "Home directory of the user") L.SetField(mod, "home", lua.LString(homedir))
util.SetField(L, mod, "dataDir", lua.LString(dataDir), "Directory for Hilbish's data files") L.SetField(mod, "dataDir", lua.LString(dataDir))
util.SetField(L, mod, "interactive", lua.LBool(interactive), "If this is an interactive shell") L.SetField(mod, "interactive", lua.LBool(interactive))
util.SetField(L, mod, "login", lua.LBool(interactive), "Whether this is a login shell") L.SetField(mod, "login", lua.LBool(interactive))
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,7 +32,6 @@ 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))
@ -59,13 +58,6 @@ 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
@ -148,7 +140,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 + string(os.PathListSeparator) + dir) os.Setenv("PATH", pathenv + ":" + dir)
} }
return 0 return 0
@ -234,18 +226,3 @@ 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,12 +265,13 @@ 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,90 +49,41 @@ 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 = args[1] local mod = table.concat(args, ' '):gsub('^%s*(.-)%s*$', '%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
-- assume subdir print('Could not find docs for module named ' .. mod .. '.')
-- dataDir/docs/<mod>/<mod>.txt return 1
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
if not funcdocs then local desc = (mod == 'global' and globalDesc or getmetatable(require(mod)).__doc)
funcdocs = f:read '*a' local funcdocs = f:read '*a'
end
local desc = ''
local ok = pcall(require, mod)
local backtickOccurence = 0 local backtickOccurence = 0
local formattedFuncs = lunacolors.format(funcdocs:sub(1, #funcdocs - 1):gsub('`', function() print(desc .. '\n\n' .. 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(string.gsub(f, '.txt', ''))) return lunacolors.underline(lunacolors.blue(f:sub(1, -5)))
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 <section> [subdoc] Usage: doc <module>
A section is a module or a literal section and a subdoc is a subsection for it.
Available sections: ]] Available modules: ]]
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,24 +25,12 @@ 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, ctx := splitInput(ctx) fields := strings.Split(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,13 +124,6 @@ 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
@ -140,6 +133,10 @@ 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,31 +2,9 @@ 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.GetMetatable(module) mt := L.NewTable()
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)
}