Compare commits

...

11 Commits

Author SHA1 Message Date
sammyette e7c4b2dc16
Merge eded38c7b5 into 1e01580d8f 2024-09-06 06:45:05 -04:00
ShalokShalom 1e01580d8f
docs: add info about runner mode (#325) 2024-08-31 18:05:24 -04:00
sammyette edbc758c67
docs: use only 1 screenshot 2024-08-31 16:50:58 -04:00
TorchedSammy 824f5bd06d docs: [ci] generate new docs 2024-08-16 19:26:30 +00:00
sammyette a7ba2fdf1a
fix: add newline at the end of input if in an unfinished heredoc (#322)
and define (or fix) the behavior of input ending with a slash
it will now add a newline at the end of input always
2024-08-16 15:26:10 -04:00
sammyette e6b88816fd
chore: add 2.4 motd (work in progress) 2024-08-16 15:25:35 -04:00
sammyette db851cf4f8
chore: bump to 2.4 2024-08-16 15:23:55 -04:00
sammyette fc6a9a33e1
chore: update dependencies (fixes #318) 2024-08-15 12:38:35 -04:00
sammyette 0582fbd30c
chore: prepare v2.3.2 2024-07-30 19:26:35 -04:00
sammyette 137efe5c62
fix: motd typo 2024-07-30 19:24:57 -04:00
sammyette eded38c7b5
feat: add completions for sudo 2023-12-02 12:35:12 -04:00
13 changed files with 176 additions and 76 deletions

View File

@ -1,10 +1,21 @@
# 🎀 Changelog # 🎀 Changelog
## Unreleased
### Fixed
- Heredocs having issues
### Added
- Adding `\` at the end of input will add a newline and prompt for more input.
## [2.3.2] - 2024-07-30
### Fixed
- Command path searching due to 2.3 changes to the shell interpreter
## [2.3.1] - 2024-07-27 ## [2.3.1] - 2024-07-27
[hehe when you see it release](https://youtu.be/AaAF51Gwbxo?si=rhj2iYuQRkqDa693&t=64) [hehe when you see it release](https://youtu.be/AaAF51Gwbxo?si=rhj2iYuQRkqDa693&t=64)
### Added ### Added
- `nature.opts.tips` was added to display random tips on start up. - `hilbish.opts.tips` was added to display random tips on start up.
Displayed tips can be modified via the `hilbish.tips` table. Displayed tips can be modified via the `hilbish.tips` table.
### Fixed ### Fixed
@ -769,6 +780,7 @@ This input for example will prompt for more input to complete:
First "stable" release of Hilbish. First "stable" release of Hilbish.
[2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.1...v2.3.2
[2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.0...v2.3.1 [2.3.1]: https://github.com/Rosettea/Hilbish/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/Rosettea/Hilbish/compare/v2.2.3...v2.3.0 [2.3.0]: https://github.com/Rosettea/Hilbish/compare/v2.2.3...v2.3.0
[2.2.3]: https://github.com/Rosettea/Hilbish/compare/v2.2.2...v2.2.3 [2.2.3]: https://github.com/Rosettea/Hilbish/compare/v2.2.2...v2.2.3

View File

@ -13,19 +13,23 @@
<br> <br>
Hilbish is an extensible shell designed to be highly customizable. Hilbish is an extensible shell designed to be highly customizable.
It is configured in Lua and provides a good range of features.
It aims to be easy to use for anyone but powerful enough for It is configured in Lua, and provides a good range of features.
those who need it. It aims to be easy to use for anyone, and powerful enough for
those who need more.
The motivation for choosing Lua was that its simpler and better to use The motivation for choosing Lua was that its simpler and better to use
than old shell script. It's fine for basic interactive shell uses, than old shell scripts. It's fine for basic interactive shell uses,
but that's the only place Hilbish has shell script; everything else is Lua and supports [both Lua and Sh interactively](https://rosettea.github.io/Hilbish/docs/features/runner-mode/).
and aims to be infinitely configurable. If something isn't, open an issue!
That's the only place Hilbish can use traditional shell syntax though;
everything else is Lua and aims to be infinitely configurable.
If something isn't, open an issue!
# Screenshots # Screenshots
<div align="center"> <div align="center">
<img src="gallery/tab.png"> <img src="gallery/tab.png">
<img src="gallery/pillprompt.png">
</div> </div>
# Getting Hilbish # Getting Hilbish

View File

@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return. All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.) (So if there isn't an error, just omit `err`.)
- `exitCode` (number): A numerical code to indicate the exit result. - `exitCode` (number): Exit code of the command
- `input` (string): The user input. This will be used to add - `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
to the history. more is requested.
- `err` (string): A string to indicate an interal error for the runner. - `err` (string): A string that represents an error from the runner.
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message: This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is. hooks and have a better looking message.
- `\<command>: not-found` will throw a `command.not-found` hook
`[command]: not-executable` will throw a command.not-executable hook. based on what `\<command>` is.
- `continue` (boolean): Whether to prompt the user for more input. - `\<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error. shell script if fennel eval has an error.

View File

@ -33,19 +33,6 @@ needs to run interactive input. For more detail, see the [API documentation](../
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode` The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the shell script and Lua runner functions that Hilbish itself uses. and also provides the shell script and Lua runner functions that Hilbish itself uses.
A runner function is expected to return a table with the following values:
- `exitCode` (number): Exit code of the command
- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
more is requested.
- `err` (string): A string that represents an error from the runner.
This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
hooks and have a better looking message.
- `<command>: not-found` will throw a `command.not-found` hook
based on what `<command>` is.
- `<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
## Functions ## Functions
These are the "low level" functions for the `hilbish.runner` interface. These are the "low level" functions for the `hilbish.runner` interface.

56
exec.go
View File

@ -98,6 +98,7 @@ func runInput(input string, priv bool) {
var exitCode uint8 var exitCode uint8
var err error var err error
var cont bool var cont bool
var newline bool
// save incase it changes while prompting (For some reason) // save incase it changes while prompting (For some reason)
currentRunner := runnerMode currentRunner := runnerMode
if currentRunner.Type() == rt.StringType { if currentRunner.Type() == rt.StringType {
@ -108,9 +109,9 @@ func runInput(input string, priv bool) {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
} }
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, newline, err = handleSh(input)
case "hybridRev": case "hybridRev":
_, _, _, err = handleSh(input) _, _, _, _, err = handleSh(input)
if err == nil { if err == nil {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
@ -119,12 +120,12 @@ func runInput(input string, priv bool) {
case "lua": case "lua":
input, exitCode, err = handleLua(input) input, exitCode, err = handleLua(input)
case "sh": case "sh":
input, exitCode, cont, err = handleSh(input) input, exitCode, cont, newline, err = handleSh(input)
} }
} else { } else {
// can only be a string or function so // can only be a string or function so
var runnerErr error var runnerErr error
input, exitCode, cont, runnerErr, err = runLuaRunner(currentRunner, input) input, exitCode, cont, newline, runnerErr, err = runLuaRunner(currentRunner, input)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
cmdFinish(124, input, priv) cmdFinish(124, input, priv)
@ -137,15 +138,15 @@ func runInput(input string, priv bool) {
} }
if cont { if cont {
input, err = reprompt(input) input, err = continuePrompt(input, newline)
if err == nil { if err == nil {
goto rerun goto rerun
} else if err == io.EOF { } else if err == io.EOF {
return lr.SetPrompt(fmtPrompt(prompt))
} }
} }
if err != nil { if err != nil && err != io.EOF {
if exErr, ok := isExecError(err); ok { if exErr, ok := isExecError(err); ok {
hooks.Emit("command." + exErr.typ, exErr.cmd) hooks.Emit("command." + exErr.typ, exErr.cmd)
} else { } else {
@ -155,26 +156,28 @@ func runInput(input string, priv bool) {
cmdFinish(exitCode, input, priv) cmdFinish(exitCode, input, priv)
} }
func reprompt(input string) (string, error) { func reprompt(input string, newline bool) (string, error) {
for { for {
in, err := continuePrompt(strings.TrimSuffix(input, "\\")) /*
if strings.HasSuffix(input, "\\") {
input = strings.TrimSuffix(input, "\\") + "\n"
}
*/
in, err := continuePrompt(input, newline)
if err != nil { if err != nil {
lr.SetPrompt(fmtPrompt(prompt)) lr.SetPrompt(fmtPrompt(prompt))
return input, err return input, err
} }
if strings.HasSuffix(in, "\\") {
continue
}
return in, nil return in, nil
} }
} }
func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, runnerErr, err error) { func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, newline bool, runnerErr, err error) {
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false) term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term) err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
if err != nil { if err != nil {
return "", 124, false, nil, err return "", 124, false, false, nil, err
} }
var runner *rt.Table var runner *rt.Table
@ -202,6 +205,10 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok { if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
continued = c continued = c
} }
if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
newline = nl
}
return return
} }
@ -232,35 +239,40 @@ func handleLua(input string) (string, uint8, error) {
return cmdString, 125, err return cmdString, 125, err
} }
func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr error) { func handleSh(cmdString string) (input string, exitCode uint8, cont bool, newline bool, runErr error) {
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh")) shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
var err error var err error
input, exitCode, cont, runErr, err = runLuaRunner(shRunner, cmdString) input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
if err != nil { if err != nil {
runErr = err runErr = err
} }
return return
} }
func execSh(cmdString string) (string, uint8, bool, error) { func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
_, _, err := execCommand(cmdString, nil) _, _, err := execCommand(cmdString, nil)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) { if syntax.IsIncomplete(err) {
if !interactive { if !interactive {
return cmdString, 126, false, err return cmdString, 126, false, false, err
} }
return cmdString, 126, true, err
newline := false
if strings.Contains(err.Error(), "unclosed here-document") {
newline = true
}
return cmdString, 126, true, newline, err
} else { } else {
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
return cmdString, code, false, nil return cmdString, code, false, false, nil
} else { } else {
return cmdString, 126, false, err return cmdString, 126, false, false, err
} }
} }
} }
return cmdString, 0, false, nil return cmdString, 0, false, false, nil
} }
// Run command in sh interpreter // Run command in sh interpreter

2
go.mod
View File

@ -28,7 +28,7 @@ require (
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
) )
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240720131751-805c301321fd replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
replace github.com/maxlandon/readline => ./readline replace github.com/maxlandon/readline => ./readline

4
go.sum
View File

@ -1,7 +1,7 @@
github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749 h1:jIFnWBTsYw8s7RX7H2AOXjDVhWP3ol7OzUVaPN2KnGI= github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749 h1:jIFnWBTsYw8s7RX7H2AOXjDVhWP3ol7OzUVaPN2KnGI=
github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE= github.com/Rosettea/golua v0.0.0-20240427174124-d239074c1749/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240720131751-805c301321fd h1:THNle0FR2g7DMO1y3Bx1Zr7rYeiLXt3st3UkxEsMzL4= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73 h1:zTTUJqNnrF2qf4LgygN8Oae5Uxn6ewH0hA8jyTCHfXw=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240720131751-805c301321fd/go.mod h1:YZalN5H7WNQw3DGij6IvHsEhn5YMW7M2FCwG6gnfKy4= github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73/go.mod h1:YZalN5H7WNQw3DGij6IvHsEhn5YMW7M2FCwG6gnfKy4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw= github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=

17
main.go
View File

@ -223,8 +223,9 @@ input:
} }
if strings.HasSuffix(input, "\\") { if strings.HasSuffix(input, "\\") {
print("\n")
for { for {
input, err = continuePrompt(input) input, err = continuePrompt(strings.TrimSuffix(input, "\\") + "\n", false)
if err != nil { if err != nil {
running = true running = true
lr.SetPrompt(fmtPrompt(prompt)) lr.SetPrompt(fmtPrompt(prompt))
@ -248,16 +249,24 @@ input:
exit(0) exit(0)
} }
func continuePrompt(prev string) (string, error) { func continuePrompt(prev string, newline bool) (string, error) {
hooks.Emit("multiline", nil) hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt) lr.SetPrompt(multilinePrompt)
cont, err := lr.Read() cont, err := lr.Read()
if err != nil { if err != nil {
return "", err return "", err
} }
cont = strings.TrimSpace(cont)
return prev + strings.TrimSuffix(cont, "\n"), nil if newline {
cont = "\n" + cont
}
if strings.HasSuffix(cont, "\\") {
cont = strings.TrimSuffix(cont, "\\") + "\n"
}
return prev + cont, nil
} }
// This semi cursed function formats our prompt (obviously) // This semi cursed function formats our prompt (obviously)

View File

@ -1,3 +1,23 @@
local fs = require 'fs'
-- explanation: this specific function gives to us info about
-- the currently running source. this includes a path to the
-- source file (info.source)
-- we will use that to automatically load all commands by reading
-- all the files in this dir and just requiring it.
local info = debug.getinfo(1)
local commandDir = fs.dir(info.source)
if commandDir == '.' then return end
local commands = fs.readdir(commandDir)
for _, command in ipairs(commands) do
local name = command:gsub('%.lua', '') -- chop off extension
if name ~= 'init' then
-- skip this file (for obvious reasons)
require('nature.completions.' .. name)
end
end
function hilbish.completion.handler(line, pos) function hilbish.completion.handler(line, pos)
if type(line) ~= 'string' then error '#1 must be a string' end if type(line) ~= 'string' then error '#1 must be a string' end
if type(pos) ~= 'number' then error '#2 must be a number' end if type(pos) ~= 'number' then error '#2 must be a number' end

View File

@ -0,0 +1,53 @@
local function curry(f)
return function (x) return function (y) return f(x,y) end end
end
local flags = {}
local function flag(f, description)
flags[f] = {description}
end
local addflag = curry(flag)
addflag '-A' 'Ask for password via askpass or $SUDO_ASKPASS'
addflag '-B' 'Ring the bell as part of the password prompt.'
hilbish.complete('command.sudo', function(query, ctx, fields)
table.remove(fields, 1)
local nonflags = table.filter(fields, function(v)
if v == '' then
return false
end
return v:match '^%-' == nil
end)
if #fields == 1 or #nonflags == 0 then
-- complete commands or sudo flags
if query:match ('^%-') then
local compFlags = {}
for flg, flgstuff in pairs(flags) do
if flg:match('^' .. query) then
compFlags[flg] = flgstuff
end
end
local compGroup = {
items = compFlags,
type = 'list'
}
return {compGroup}, query
end
local comps, pfx = hilbish.completion.bins(query, ctx, fields)
local compGroup = {
items = comps,
type = 'grid'
}
return {compGroup}, pfx
end
-- otherwise, get command flags
return hilbish.completion.call('command.' .. fields[2], query, ctx, fields)
end)

View File

@ -2,9 +2,7 @@ local bait = require 'bait'
local lunacolors = require 'lunacolors' local lunacolors = require 'lunacolors'
hilbish.motd = [[ hilbish.motd = [[
Wait ... {magenta}2.3{reset} is basically the same as {red}2.2?{reset} {magenta}Hilbish{reset} blooms in the {blue}midnight.{reset}
Erm.. {blue}Ctrl-C works for Commanders,{reset} {cyan}and the sh runner has some fixes.{reset}
Just trust me bro, this is an imporant bug fix release. {red}- 🌺 sammyette{reset}
]] ]]
bait.catch('hilbish.init', function() bait.catch('hilbish.init', function()

View File

@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return. All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.) (So if there isn't an error, just omit `err`.)
- `exitCode` (number): A numerical code to indicate the exit result. - `exitCode` (number): Exit code of the command
- `input` (string): The user input. This will be used to add - `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
to the history. more is requested.
- `err` (string): A string to indicate an interal error for the runner. - `err` (string): A string that represents an error from the runner.
It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message: This should only be set when, for example, there is a syntax error.
It can be set to a few special values for Hilbish to throw the right
`[command]: not-found` will throw a command.not-found hook based on what `[command]` is. hooks and have a better looking message.
- `<command>: not-found` will throw a `command.not-found` hook
`[command]: not-executable` will throw a command.not-executable hook. based on what `<command>` is.
- `continue` (boolean): Whether to prompt the user for more input. - `<command>: not-executable` will throw a `command.not-executable` hook.
- `continue` (boolean): Whether Hilbish should prompt the user for no input
- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error. shell script if fennel eval has an error.
@ -85,7 +87,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
_, exitCode, cont, err := execSh(aliases.Resolve(cmd)) _, exitCode, cont, newline, err := execSh(aliases.Resolve(cmd))
var luaErr rt.Value = rt.NilValue var luaErr rt.Value = rt.NilValue
if err != nil { if err != nil {
luaErr = rt.StringValue(err.Error()) luaErr = rt.StringValue(err.Error())
@ -94,6 +96,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd)) runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode))) runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont)) runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
runnerRet.Set(rt.StringValue("err"), luaErr) runnerRet.Set(rt.StringValue("err"), luaErr)
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil

View File

@ -11,8 +11,8 @@ var (
// Version info // Version info
var ( var (
ver = "v2.3.1" ver = "v2.4.0"
releaseName = "Alyssum" releaseName = "Moonflower"
gitCommit string gitCommit string
gitBranch string gitBranch string