mirror of https://github.com/Hilbis/Hilbish
Compare commits
No commits in common. "65435572d4d329997931401b4a627061ccadcd17" and "123f8992b1b258bd649ea351ecb25ea9afc43dff" have entirely different histories.
65435572d4
...
123f8992b1
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
|
@ -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`.
|
|
|
@ -1,3 +0,0 @@
|
||||||
+ `signal.sigint` > Sent when Hilbish receives SIGINT (used to Ctrl-C).
|
|
||||||
|
|
||||||
+ `signal.resize` > Sent when the terminal is resized.
|
|
|
@ -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
|
||||||
|
|
14
hilbish.go
14
hilbish.go
|
@ -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
25
lua.go
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
5
main.go
5
main.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
71
preload.lua
71
preload.lua
|
@ -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
|
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
|
||||||
|
|
||||||
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
18
rl.go
|
@ -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 {
|
||||||
|
|
11
shell.go
11
shell.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
28
util/util.go
28
util/util.go
|
@ -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))
|
||||||
|
|
||||||
|
L.SetMetatable(module, mt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
docProp := L.GetTable(mt, lua.LString("__docProp"))
|
|
||||||
|
|
||||||
L.SetField(docProp, field, lua.LString(doc))
|
|
||||||
L.SetField(module, field, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue