mirror of
				https://github.com/sammy-ette/Hilbish
				synced 2025-08-10 02:52:03 +00:00 
			
		
		
		
	feat: add configurable runner mode (closes #110)
This commit is contained in:
		
							parent
							
								
									96c1487bfa
								
							
						
					
					
						commit
						86a15e6363
					
				
							
								
								
									
										33
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								api.go
									
									
									
									
									
								
							| @ -26,6 +26,7 @@ var exports = map[string]lua.LGFunction { | ||||
| 	"complete": hlcomplete, | ||||
| 	"cwd": hlcwd, | ||||
| 	"exec": hlexec, | ||||
| 	"runnerMode": hlrunnerMode, | ||||
| 	"goro": hlgoro, | ||||
| 	"multiprompt": hlmlprompt, | ||||
| 	"prependPath": hlprependPath, | ||||
| @ -106,6 +107,10 @@ Check out the {blue}{bold}guide{reset} command to get started. | ||||
| 	util.Document(L, hshcomp, "Completions interface for Hilbish.") | ||||
| 	L.SetField(mod, "completion", hshcomp) | ||||
| 
 | ||||
| 	runnerModule := runnerModeLoader(L) | ||||
| 	util.Document(L, runnerModule, "Runner/exec interface for Hilbish.") | ||||
| 	L.SetField(mod, "runner", runnerModule) | ||||
| 
 | ||||
| 	jobs = newJobHandler() | ||||
| 
 | ||||
| 	L.Push(mod) | ||||
| @ -172,7 +177,7 @@ func unsetVimMode() { | ||||
| func hlrun(L *lua.LState) int { | ||||
| 	var exitcode uint8 | ||||
| 	cmd := L.CheckString(1) | ||||
| 	err := execCommand(cmd, true) | ||||
| 	err := execCommand(cmd) | ||||
| 
 | ||||
| 	if code, ok := interp.IsExitStatus(err); ok { | ||||
| 		exitcode = code | ||||
| @ -466,3 +471,29 @@ func hlinputMode(L *lua.LState) int { | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
| 
 | ||||
| // runnerMode(mode) | ||||
| // Sets the execution/runner mode for interactive Hilbish. This determines whether | ||||
| // Hilbish wll try to run input as Lua and/or sh or only do one of either. | ||||
| // Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua), | ||||
| // sh, and lua. It also accepts a function, to which if it is passed one | ||||
| // will call it to execute user input instead. | ||||
| func hlrunnerMode(L *lua.LState) int { | ||||
| 	mode := L.CheckAny(1) | ||||
| 	switch mode.Type() { | ||||
| 		case lua.LTString: | ||||
| 			switch mode.String() { | ||||
| 				// no fallthrough doesnt work so eh | ||||
| 				case "hybrid": fallthrough | ||||
| 				case "hybridRev": fallthrough | ||||
| 				case "lua": fallthrough | ||||
| 				case "sh": | ||||
| 					runnerMode = mode | ||||
| 				default: L.RaiseError("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received %v", mode) | ||||
| 			} | ||||
| 		case lua.LTFunction: runnerMode = mode | ||||
| 		default: L.RaiseError("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received %v", mode) | ||||
| 	} | ||||
| 
 | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
							
								
								
									
										33
									
								
								docs/runner-mode.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								docs/runner-mode.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| Hilbish is *unique,* when interactive it first attempts to run input as | ||||
| Lua and then tries shell script. But if you're normal, you wouldn't | ||||
| really be using Hilbish anyway but you'd also not want this | ||||
| (or maybe want Lua only in some cases.) | ||||
| 
 | ||||
| The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`, | ||||
| which determines how Hilbish will run user input. By default, this is | ||||
| set to `hybrid` which is the previously mentioned behaviour of running Lua | ||||
| first then going to shell script. If you want the reverse order, you can | ||||
| set it to `hybridRev` and for isolated modes there is `sh` and `lua` | ||||
| respectively. | ||||
| 
 | ||||
| You can also set it to a function, which will be called everytime Hilbish | ||||
| needs to run interactive input. For example, you can set this to a simple | ||||
| function to compile and evaluate Fennel, and now you can run Fennel. | ||||
| You can even mix it with sh to make a hybrid mode with Lua replaced by | ||||
| Fennel. | ||||
| 
 | ||||
| An example: | ||||
| hilbish.runnerMode(function(input) | ||||
| 	local ok = pcall(fennel.eval, input) | ||||
| 	if ok then | ||||
| 		return 0, nil | ||||
| 	end | ||||
| 
 | ||||
| 	return hilbish.runner.sh(input) | ||||
| end) | ||||
| 
 | ||||
| The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode` | ||||
| and also provides the sh and Lua runner functions that Hilbish itself uses. | ||||
| A runner function is expected to return 2 values: the exit code, and an error. | ||||
| The exit code has to be a number, it will be 0 otherwise and the error can be | ||||
| `nil` to indicate no error. | ||||
							
								
								
									
										105
									
								
								exec.go
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								exec.go
									
									
									
									
									
								
							| @ -24,13 +24,80 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| var errNotExec = errors.New("not executable") | ||||
| var runnerMode lua.LValue = lua.LString("hybrid") | ||||
| 
 | ||||
| func runInput(input string, priv bool) { | ||||
| 	running = true | ||||
| 	cmdString := aliases.Resolve(input) | ||||
| 
 | ||||
| 	hooks.Em.Emit("command.preexec", input, cmdString) | ||||
| 
 | ||||
| 	if runnerMode.Type() == lua.LTString { | ||||
| 		switch runnerMode.String() { | ||||
| 			case "hybrid": | ||||
| 				_, err := handleLua(cmdString) | ||||
| 				if err == nil { | ||||
| 					cmdFinish(0, cmdString, priv) | ||||
| 					return | ||||
| 				} | ||||
| 				exitCode, err := handleSh(cmdString) | ||||
| 				if err != nil { | ||||
| 					fmt.Fprintln(os.Stderr, err) | ||||
| 				} | ||||
| 				cmdFinish(exitCode, cmdString, priv) | ||||
| 			case "hybridRev": | ||||
| 				_, err := handleSh(cmdString) | ||||
| 				if err == nil { | ||||
| 					cmdFinish(0, cmdString, priv) | ||||
| 					return | ||||
| 				} | ||||
| 				exitCode, err := handleLua(cmdString) | ||||
| 				if err != nil { | ||||
| 					fmt.Fprintln(os.Stderr, err) | ||||
| 				} | ||||
| 				cmdFinish(exitCode, cmdString, priv) | ||||
| 			case "lua": | ||||
| 				exitCode, err := handleLua(cmdString) | ||||
| 				if err != nil { | ||||
| 					fmt.Fprintln(os.Stderr, err) | ||||
| 				} | ||||
| 				cmdFinish(exitCode, cmdString, priv) | ||||
| 			case "sh": | ||||
| 				exitCode, err := handleSh(cmdString) | ||||
| 				if err != nil { | ||||
| 					fmt.Fprintln(os.Stderr, err) | ||||
| 				} | ||||
| 				cmdFinish(exitCode, cmdString, priv) | ||||
| 		} | ||||
| 	} else { | ||||
| 		// can only be a string or function so | ||||
| 		err := l.CallByParam(lua.P{ | ||||
| 			Fn: runnerMode, | ||||
| 			NRet: 2, | ||||
| 			Protect: true, | ||||
| 		}, lua.LString(cmdString)) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintln(os.Stderr, err) | ||||
| 			cmdFinish(124, cmdString, priv) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		luaexitcode := l.Get(-2) // first return value (makes sense right i love stacks) | ||||
| 		runErr := l.Get(-1) | ||||
| 		l.Pop(2) | ||||
| 
 | ||||
| 		var exitCode uint8 | ||||
| 		if code, ok := luaexitcode.(lua.LNumber); luaexitcode != lua.LNil && ok { | ||||
| 			exitCode = uint8(code) | ||||
| 		} | ||||
| 
 | ||||
| 		if runErr != lua.LNil { | ||||
| 			fmt.Fprintln(os.Stderr, runErr) | ||||
| 		} | ||||
| 		cmdFinish(exitCode, cmdString, priv) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func handleLua(cmdString string) (uint8, error) { | ||||
| 	// First try to load input, essentially compiling to bytecode | ||||
| 	fn, err := l.LoadString(cmdString) | ||||
| 	if err != nil && noexecute { | ||||
| @ -41,7 +108,7 @@ func runInput(input string, priv bool) { | ||||
| 			} | ||||
| 		} | ||||
| 	*/ | ||||
| 		return | ||||
| 		return 125, err | ||||
| 	} | ||||
| 	// And if there's no syntax errors and -n isnt provided, run | ||||
| 	if !noexecute { | ||||
| @ -49,12 +116,14 @@ func runInput(input string, priv bool) { | ||||
| 		err = l.PCall(0, lua.MultRet, nil) | ||||
| 	} | ||||
| 	if err == nil { | ||||
| 		cmdFinish(0, cmdString, priv) | ||||
| 		return | ||||
| 		return 0, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Last option: use sh interpreter | ||||
| 	err = execCommand(cmdString, priv) | ||||
| 	return 125, err | ||||
| } | ||||
| 
 | ||||
| func handleSh(cmdString string) (uint8, error) { | ||||
| 	err := execCommand(cmdString) | ||||
| 	if err != nil { | ||||
| 		// If input is incomplete, start multiline prompting | ||||
| 		if syntax.IsIncomplete(err) { | ||||
| @ -63,34 +132,31 @@ func runInput(input string, priv bool) { | ||||
| 				if err != nil { | ||||
| 					break | ||||
| 				} | ||||
| 				err = execCommand(cmdString, priv) | ||||
| 				if syntax.IsIncomplete(err) || strings.HasSuffix(input, "\\") { | ||||
| 				err = execCommand(cmdString) | ||||
| 				if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") { | ||||
| 					continue | ||||
| 				} else if code, ok := interp.IsExitStatus(err); ok { | ||||
| 					cmdFinish(code, cmdString, priv) | ||||
| 					return code, nil | ||||
| 				} else if err != nil { | ||||
| 					cmdFinish(1, cmdString, priv) | ||||
| 					fmt.Fprintln(os.Stderr, err) | ||||
| 					return 126, err | ||||
| 				} else { | ||||
| 					cmdFinish(0, cmdString, priv) | ||||
| 					return 0, nil | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} else { | ||||
| 			if code, ok := interp.IsExitStatus(err); ok { | ||||
| 				cmdFinish(code, cmdString, priv) | ||||
| 				return code, nil | ||||
| 			} else { | ||||
| 				cmdFinish(126, cmdString, priv) | ||||
| 				fmt.Fprintln(os.Stderr, err) | ||||
| 				return 126, err | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		cmdFinish(0, cmdString, priv) | ||||
| 	} | ||||
| 
 | ||||
| 	return 0, nil | ||||
| } | ||||
| 
 | ||||
| // Run command in sh interpreter | ||||
| func execCommand(cmd string, priv bool) error { | ||||
| func execCommand(cmd string) error { | ||||
| 	file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @ -141,7 +207,6 @@ func execCommand(cmd string, priv bool) error { | ||||
| 				exitcode = uint8(code) | ||||
| 			} | ||||
| 
 | ||||
| 			cmdFinish(exitcode, argstring, priv) | ||||
| 			return interp.NewExitStatus(exitcode) | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										46
									
								
								runnermode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								runnermode.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| ) | ||||
| 
 | ||||
| func runnerModeLoader(L *lua.LState) *lua.LTable { | ||||
| 	exports := map[string]lua.LGFunction{ | ||||
| 		"sh": shRunner, | ||||
| 		"lua": luaRunner, | ||||
| 		"setMode": hlrunnerMode, | ||||
| 	} | ||||
| 
 | ||||
| 	mod := L.SetFuncs(L.NewTable(), exports) | ||||
| 	L.SetField(mod, "mode", runnerMode) | ||||
| 
 | ||||
| 	return mod | ||||
| } | ||||
| 
 | ||||
| func shRunner(L *lua.LState) int { | ||||
| 	cmd := L.CheckString(1) | ||||
| 	exitCode, err := handleSh(cmd) | ||||
| 	var luaErr lua.LValue = lua.LNil | ||||
| 	if err != nil { | ||||
| 		luaErr = lua.LString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	L.Push(lua.LNumber(exitCode)) | ||||
| 	L.Push(luaErr) | ||||
| 
 | ||||
| 	return 2 | ||||
| } | ||||
| 
 | ||||
| func luaRunner(L *lua.LState) int { | ||||
| 	cmd := L.CheckString(1) | ||||
| 	exitCode, err := handleLua(cmd) | ||||
| 	var luaErr lua.LValue = lua.LNil | ||||
| 	if err != nil { | ||||
| 		luaErr = lua.LString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	L.Push(lua.LNumber(exitCode)) | ||||
| 	L.Push(luaErr) | ||||
| 
 | ||||
| 	return 2 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user