diff --git a/lua.go b/lua.go index b8d0d9b..a5f3b37 100644 --- a/lua.go +++ b/lua.go @@ -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)) diff --git a/main.go b/main.go index 3d2f950..31aecfa 100644 --- a/main.go +++ b/main.go @@ -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 @@ -104,77 +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) - readline.SaveHistory(homedir + "/.hilbish-history") - 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) - readline.SaveHistory(homedir + "/.hilbish-history") - 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) } } @@ -204,101 +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) - readline.SaveHistory(homedir + "/.hilbish-history") - 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) diff --git a/shell.go b/shell.go new file mode 100644 index 0000000..12519bf --- /dev/null +++ b/shell.go @@ -0,0 +1,186 @@ +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.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.fail", code) + } + } + fmt.Fprintln(os.Stderr, err) + } + } else { + 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 +}