Compare commits

...

5 Commits

Author SHA1 Message Date
Jack 6c050194ed
feat: Proxy global string variables to ENV (#16) 2021-04-03 16:08:04 -04:00
Jack ad1be6b5f5
feat: Add command.exit signal to bait (#15) 2021-04-03 14:33:38 -04:00
TorchedSammy 71504c9990 chore: add a "motd" 2021-04-03 13:14:25 -04:00
TorchedSammy 4f4237a3e9 chore: split up go source files 2021-04-03 13:13:45 -04:00
TorchedSammy a0bc8e026a feat: save history to file 2021-04-01 22:12:03 -04:00
5 changed files with 236 additions and 170 deletions

View File

@ -8,6 +8,10 @@ function doPrompt(fail)
))
end
print(ansikit.text('Welcome {cyan}'.. os.getenv 'USER' ..
'{reset} to {magenta}Hilbish{reset},\n' ..
'the nice lil shell for {blue}Lua{reset} fanatics!\n'))
doPrompt()
bait.catch('command.fail', function()

2
lua.go
View File

@ -15,6 +15,8 @@ func LuaInit() {
l.OpenLibs()
l.SetGlobal("_ver", lua.LString(version))
l.SetGlobal("prompt", l.NewFunction(hshprompt))
l.SetGlobal("alias", l.NewFunction(hshalias))

175
main.go
View File

@ -1,7 +1,6 @@
package main
import (
"bufio"
"fmt"
"os"
"os/user"
@ -9,19 +8,15 @@ import (
"os/signal"
"strings"
"io"
"context"
hooks "hilbish/golibs/bait"
"github.com/akamensky/argparse"
"github.com/bobappleyard/readline"
"github.com/yuin/gopher-lua"
"layeh.com/gopher-luar"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
)
const version = "0.2.0"
const version = "0.3.0-dev"
var l *lua.LState
// User's prompt, this will get set when lua side is initialized
var prompt string
@ -30,6 +25,7 @@ var commands = map[string]bool{}
// Command aliases
var aliases = map[string]string{}
var bait hooks.Bait
var homedir string
func main() {
parser := argparse.NewParser("hilbish", "A shell for lua and flower lovers")
@ -58,7 +54,7 @@ func main() {
// Set $SHELL if the user wants to
if *setshflag { os.Setenv("SHELL", os.Args[0]) }
homedir, _ := os.UserHomeDir()
homedir, _ = os.UserHomeDir()
// If user's config doesn't exixt,
if _, err := os.Stat(homedir + "/.hilbishrc.lua"); os.IsNotExist(err) {
// Read default from current directory
@ -87,6 +83,7 @@ func main() {
LuaInit()
readline.Completer = readline.FilenameCompleter
readline.LoadHistory(homedir + "/.hilbish-history")
for {
cmdString, err := readline.String(fmtPrompt())
@ -102,75 +99,7 @@ func main() {
// I have no idea if we need this anymore
cmdString = strings.TrimSuffix(cmdString, "\n")
// First try to run user input in Lua
err = l.DoString(cmdString)
if err == nil {
// If it succeeds, add to history and prompt again
readline.AddHistory(cmdString)
bait.Em.Emit("command.success", nil)
continue
}
// Split up the input
cmdArgs, cmdString := splitInput(cmdString)
// If there's actually no input, prompt again
if len(cmdArgs) == 0 { continue }
// If alias was found, use command alias
if aliases[cmdArgs[0]] != "" {
cmdString = aliases[cmdArgs[0]] + strings.Trim(cmdString, cmdArgs[0])
execCommand(cmdString)
continue
}
// If command is defined in Lua then run it
if commands[cmdArgs[0]] {
err := l.CallByParam(lua.P{
Fn: l.GetField(
l.GetTable(
l.GetGlobal("commanding"),
lua.LString("__commands")),
cmdArgs[0]),
NRet: 0,
Protect: true,
}, luar.New(l, cmdArgs[1:]))
if err != nil {
// TODO: dont panic
panic(err)
}
readline.AddHistory(cmdString)
continue
}
// Last option: use sh interpreter
switch cmdArgs[0] {
case "exit":
os.Exit(0)
default:
err := execCommand(cmdString)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
sb := &strings.Builder{}
for {
done := StartMultiline(cmdString, sb)
if done {
break
}
}
} else {
if code, ok := interp.IsExitStatus(err); ok {
if code > 0 {
bait.Em.Emit("command.fail", code)
}
}
fmt.Fprintln(os.Stderr, err)
}
} else {
bait.Em.Emit("command.success", nil)
}
}
RunInput(cmdString)
}
}
@ -200,100 +129,6 @@ func fmtPrompt() string {
return nprompt
}
func StartMultiline(prev string, sb *strings.Builder) bool {
// sb fromt outside is passed so we can
// save input from previous prompts
if sb.String() == "" { sb.WriteString(prev + "\n") }
fmt.Printf("... ")
reader := bufio.NewReader(os.Stdin)
cont, err := reader.ReadString('\n')
if err == io.EOF {
// Exit when ^D
fmt.Println("")
return true
}
sb.WriteString(cont)
err = execCommand(sb.String())
if err != nil && syntax.IsIncomplete(err) {
return false
}
return true
}
func splitInput(input string) ([]string, string) {
// end my suffering
// TODO: refactor this garbage
quoted := false
startlastcmd := false
lastcmddone := false
cmdArgs := []string{}
sb := &strings.Builder{}
cmdstr := &strings.Builder{}
lastcmd := readline.GetHistory(readline.HistorySize() - 1)
for _, r := range input {
if r == '"' {
// start quoted input
// this determines if other runes are replaced
quoted = !quoted
// dont add back quotes
//sb.WriteRune(r)
} else if !quoted && r == '~' {
// if not in quotes and ~ is found then make it $HOME
sb.WriteString(os.Getenv("HOME"))
} else if !quoted && r == ' ' {
// if not quoted and there's a space then add to cmdargs
cmdArgs = append(cmdArgs, sb.String())
sb.Reset()
} else if !quoted && r == '^' && startlastcmd && !lastcmddone {
// if ^ is found, isnt in quotes and is
// the second occurence of the character and is
// the first time "^^" has been used
cmdstr.WriteString(lastcmd)
sb.WriteString(lastcmd)
startlastcmd = !startlastcmd
lastcmddone = !lastcmddone
continue
} else if !quoted && r == '^' && !lastcmddone {
// if ^ is found, isnt in quotes and is the
// first time of starting "^^"
startlastcmd = !startlastcmd
continue
} else {
sb.WriteRune(r)
}
cmdstr.WriteRune(r)
}
if sb.Len() > 0 {
cmdArgs = append(cmdArgs, sb.String())
}
readline.AddHistory(input)
return cmdArgs, cmdstr.String()
}
// Run command in sh interpreter
func execCommand(cmd string) error {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil {
return err
}
runner, _ := interp.New(
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
)
err = runner.Run(context.TODO(), file)
return err
}
// do i even have to say
func HandleSignals() {
c := make(chan os.Signal)

View File

@ -25,3 +25,39 @@ commander.register('cd', function (args)
fs.cd(os.getenv 'HOME')
bait.throw('command.success', nil)
end)
do
local virt_G = { }
setmetatable(_G, {
__index = function (self, key)
local got_virt = virt_G[key]
if got_virt ~= nil then
return got_virt
end
virt_G[key] = os.getenv(key)
return virt_G[key]
end,
__newindex = function (self, key, value)
if type(value) == 'string' then
os.setenv(key, value)
virt_G[key] = value
else
if type(virt_G[key]) == 'string' then
os.setenv(key, '')
end
virt_G[key] = value
end
end,
})
bait.catch('command.exit', function ()
for key, value in pairs(virt_G) do
if type(value) == 'string' then
virt_G[key] = os.getenv(key)
end
end
end)
end

189
shell.go 100644
View File

@ -0,0 +1,189 @@
package main
import (
"fmt"
"os"
"bufio"
"context"
"io"
"strings"
"github.com/bobappleyard/readline"
"github.com/yuin/gopher-lua"
"layeh.com/gopher-luar"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
)
func RunInput(input string) {
// First try to run user input in Lua
err := l.DoString(input)
if err == nil {
// If it succeeds, add to history and prompt again
readline.AddHistory(input)
readline.SaveHistory(homedir + "/.hilbish-history")
bait.Em.Emit("command.exit", nil)
bait.Em.Emit("command.success", nil)
return
}
// Split up the input
cmdArgs, cmdString := splitInput(input)
// If there's actually no input, prompt again
if len(cmdArgs) == 0 { return }
// If alias was found, use command alias
if aliases[cmdArgs[0]] != "" {
cmdString = aliases[cmdArgs[0]] + strings.Trim(cmdString, cmdArgs[0])
execCommand(cmdString)
return
}
// If command is defined in Lua then run it
if commands[cmdArgs[0]] {
err := l.CallByParam(lua.P{
Fn: l.GetField(
l.GetTable(
l.GetGlobal("commanding"),
lua.LString("__commands")),
cmdArgs[0]),
NRet: 0,
Protect: true,
}, luar.New(l, cmdArgs[1:]))
if err != nil {
// TODO: dont panic
panic(err)
}
readline.AddHistory(cmdString)
readline.SaveHistory(homedir + "/.hilbish-history")
return
}
// Last option: use sh interpreter
switch cmdArgs[0] {
case "exit":
os.Exit(0)
default:
err := execCommand(cmdString)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
sb := &strings.Builder{}
for {
done := StartMultiline(cmdString, sb)
if done {
break
}
}
} else {
if code, ok := interp.IsExitStatus(err); ok {
if code > 0 {
bait.Em.Emit("command.exit", nil)
bait.Em.Emit("command.fail", code)
}
}
fmt.Fprintln(os.Stderr, err)
}
} else {
bait.Em.Emit("command.exit", nil)
bait.Em.Emit("command.success", nil)
}
}
}
// Run command in sh interpreter
func execCommand(cmd string) error {
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
if err != nil {
return err
}
runner, _ := interp.New(
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
)
err = runner.Run(context.TODO(), file)
return err
}
func splitInput(input string) ([]string, string) {
// end my suffering
// TODO: refactor this garbage
quoted := false
startlastcmd := false
lastcmddone := false
cmdArgs := []string{}
sb := &strings.Builder{}
cmdstr := &strings.Builder{}
lastcmd := readline.GetHistory(readline.HistorySize() - 1)
for _, r := range input {
if r == '"' {
// start quoted input
// this determines if other runes are replaced
quoted = !quoted
// dont add back quotes
//sb.WriteRune(r)
} else if !quoted && r == '~' {
// if not in quotes and ~ is found then make it $HOME
sb.WriteString(os.Getenv("HOME"))
} else if !quoted && r == ' ' {
// if not quoted and there's a space then add to cmdargs
cmdArgs = append(cmdArgs, sb.String())
sb.Reset()
} else if !quoted && r == '^' && startlastcmd && !lastcmddone {
// if ^ is found, isnt in quotes and is
// the second occurence of the character and is
// the first time "^^" has been used
cmdstr.WriteString(lastcmd)
sb.WriteString(lastcmd)
startlastcmd = !startlastcmd
lastcmddone = !lastcmddone
continue
} else if !quoted && r == '^' && !lastcmddone {
// if ^ is found, isnt in quotes and is the
// first time of starting "^^"
startlastcmd = !startlastcmd
continue
} else {
sb.WriteRune(r)
}
cmdstr.WriteRune(r)
}
if sb.Len() > 0 {
cmdArgs = append(cmdArgs, sb.String())
}
readline.AddHistory(input)
readline.SaveHistory(homedir + "/.hilbish-history")
return cmdArgs, cmdstr.String()
}
func StartMultiline(prev string, sb *strings.Builder) bool {
// sb fromt outside is passed so we can
// save input from previous prompts
if sb.String() == "" { sb.WriteString(prev + "\n") }
fmt.Printf("... ")
reader := bufio.NewReader(os.Stdin)
cont, err := reader.ReadString('\n')
if err == io.EOF {
// Exit when ^D
fmt.Println("")
return true
}
sb.WriteString(cont)
err = execCommand(sb.String())
if err != nil && syntax.IsIncomplete(err) {
return false
}
return true
}