mirror of
				https://github.com/sammy-ette/Hilbish
				synced 2025-08-10 02:52:03 +00:00 
			
		
		
		
	refactor: use custom event emitter (#193)
* refactor: use custom event emitter * fix: sigint hook emit on windows * fix: restore correct hilbish conf file * fix: call recoverer for go listeners * refactor(golibs/bait): use 1 map for listeners * feat: add once listeners, ability to remove listeners and remove listener on error * perf: reslice listener slice instead of trying to do ordered move with append * feat(bait): add release function to remove event listener * perf: remove listener directly from once emit instead of using off function * refactor: use bait event emitter on commander * docs(golibs/bait): add doc strings for functions * docs: set changelog * docs(golibs/bait): add docs for lua release function
This commit is contained in:
		
							parent
							
								
									6ce4fb3973
								
							
						
					
					
						commit
						2337f9ab60
					
				| @ -70,6 +70,11 @@ set `hilbish.opts.motd` to false. | ||||
| disables commands being added to history. | ||||
| - `hilbish.rawInput` hook for input from the readline library | ||||
| - Completion of files in quotes | ||||
| - A new and "safer" event emitter has been added. This causes a performance deficit, but avoids a lot of | ||||
| random errors introduced with the new Lua runtime (see [#197]) | ||||
| - `bait.release(name, catcher)` removes `handler` for the named `event` | ||||
| 
 | ||||
| [#197]: https://github.com/Rosettea/Hilbish/issues/197 | ||||
| 
 | ||||
| ### Changed | ||||
| - **Breaking Change:** Upgraded to Lua 5.4. | ||||
|  | ||||
							
								
								
									
										2
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								api.go
									
									
									
									
									
								
							| @ -189,7 +189,7 @@ func getenv(key, fallback string) string { | ||||
| 
 | ||||
| func setVimMode(mode string) { | ||||
| 	util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)") | ||||
| 	hooks.Em.Emit("hilbish.vimMode", mode) | ||||
| 	hooks.Emit("hilbish.vimMode", mode) | ||||
| } | ||||
| 
 | ||||
| func unsetVimMode() { | ||||
|  | ||||
							
								
								
									
										6
									
								
								exec.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								exec.go
									
									
									
									
									
								
							| @ -85,7 +85,7 @@ func isExecError(err error) (execError, bool) { | ||||
| func runInput(input string, priv bool) { | ||||
| 	running = true | ||||
| 	cmdString := aliases.Resolve(input) | ||||
| 	hooks.Em.Emit("command.preexec", input, cmdString) | ||||
| 	hooks.Emit("command.preexec", input, cmdString) | ||||
| 
 | ||||
| 	rerun: | ||||
| 	var exitCode uint8 | ||||
| @ -140,7 +140,7 @@ func runInput(input string, priv bool) { | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		if exErr, ok := isExecError(err); ok { | ||||
| 			hooks.Em.Emit("command." + exErr.typ, exErr.cmd) | ||||
| 			hooks.Emit("command." + exErr.typ, exErr.cmd) | ||||
| 			err = exErr.sprint() | ||||
| 		} | ||||
| 		fmt.Fprintln(os.Stderr, err) | ||||
| @ -544,5 +544,5 @@ func cmdFinish(code uint8, cmdstr string, private bool) { | ||||
| 	// using AsValue (to convert to lua type) on an interface which is an int | ||||
| 	// results in it being unknown in lua .... ???? | ||||
| 	// so we allow the hook handler to take lua runtime Values | ||||
| 	hooks.Em.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private) | ||||
| 	hooks.Emit("command.exit", rt.IntValue(int64(code)), cmdstr, private) | ||||
| } | ||||
|  | ||||
| @ -1,27 +1,41 @@ | ||||
| package bait | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"hilbish/util" | ||||
| 
 | ||||
| 	rt "github.com/arnodel/golua/runtime" | ||||
| 	"github.com/arnodel/golua/lib/packagelib" | ||||
| 	"github.com/chuckpreslar/emission" | ||||
| ) | ||||
| 
 | ||||
| type Bait struct{ | ||||
| 	Em *emission.Emitter | ||||
| 	Loader packagelib.Loader | ||||
| type listenerType int | ||||
| const ( | ||||
| 	goListener listenerType = iota | ||||
| 	luaListener | ||||
| ) | ||||
| 
 | ||||
| // Recoverer is a function which is called when a panic occurs in an event. | ||||
| type Recoverer func(event string, handler *Listener, err interface{}) | ||||
| 
 | ||||
| // Listener is a struct that holds the handler for an event. | ||||
| type Listener struct{ | ||||
| 	typ listenerType | ||||
| 	once bool | ||||
| 	caller func(...interface{}) | ||||
| 	luaCaller *rt.Closure | ||||
| } | ||||
| 
 | ||||
| func New() Bait { | ||||
| 	emitter := emission.NewEmitter() | ||||
| 	emitter.RecoverWith(func(hookname, hookfunc interface{}, err error) { | ||||
| 		emitter.Off(hookname, hookfunc) | ||||
| 		fmt.Println(err) | ||||
| 	}) | ||||
| 	b := Bait{ | ||||
| 		Em: emitter, | ||||
| type Bait struct{ | ||||
| 	Loader packagelib.Loader | ||||
| 	recoverer Recoverer | ||||
| 	handlers map[string][]*Listener | ||||
| 	rtm *rt.Runtime | ||||
| } | ||||
| 
 | ||||
| // New creates a new Bait instance. | ||||
| func New(rtm *rt.Runtime) *Bait { | ||||
| 	b := &Bait{ | ||||
| 		handlers: make(map[string][]*Listener), | ||||
| 		rtm: rtm, | ||||
| 	} | ||||
| 	b.Loader = packagelib.Loader{ | ||||
| 		Load: b.loaderFunc, | ||||
| @ -31,11 +45,148 @@ func New() Bait { | ||||
| 	return b | ||||
| } | ||||
| 
 | ||||
| // Emit throws an event. | ||||
| func (b *Bait) Emit(event string, args ...interface{}) { | ||||
| 	handles := b.handlers[event] | ||||
| 	if handles == nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, handle := range handles { | ||||
| 		defer func() { | ||||
| 			if err := recover(); err != nil { | ||||
| 				b.callRecoverer(event, handle, err) | ||||
| 			} | ||||
| 		}() | ||||
| 
 | ||||
| 		if handle.typ == luaListener { | ||||
| 			funcVal := rt.FunctionValue(handle.luaCaller) | ||||
| 			var luaArgs []rt.Value | ||||
| 			for _, arg := range args { | ||||
| 				var luarg rt.Value | ||||
| 				switch arg.(type) { | ||||
| 					case rt.Value: luarg = arg.(rt.Value) | ||||
| 					default: luarg = rt.AsValue(arg) | ||||
| 				} | ||||
| 				luaArgs = append(luaArgs, luarg) | ||||
| 			} | ||||
| 			_, err := rt.Call1(b.rtm.MainThread(), funcVal, luaArgs...) | ||||
| 			if err != nil { | ||||
| 				// panicking here won't actually cause hilbish to panic and instead will | ||||
| 				// print the error and remove the hook. reference the recoverer function in lua.go | ||||
| 				panic(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			handle.caller(args...) | ||||
| 		} | ||||
| 
 | ||||
| 		if handle.once { | ||||
| 			b.removeListener(event, idx) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // On adds a Go function handler for an event. | ||||
| func (b *Bait) On(event string, handler func(...interface{})) *Listener { | ||||
| 	listener := &Listener{ | ||||
| 		typ: goListener, | ||||
| 		caller: handler, | ||||
| 	} | ||||
| 
 | ||||
| 	b.addListener(event, listener) | ||||
| 	return listener | ||||
| } | ||||
| 
 | ||||
| // OnLua adds a Lua function handler for an event. | ||||
| func (b *Bait) OnLua(event string, handler *rt.Closure) *Listener { | ||||
| 	listener :=&Listener{ | ||||
| 		typ: luaListener, | ||||
| 		luaCaller: handler, | ||||
| 	} | ||||
| 	b.addListener(event, listener) | ||||
| 
 | ||||
| 	return listener | ||||
| } | ||||
| 
 | ||||
| // Off removes a Go function handler for an event. | ||||
| func (b *Bait) Off(event string, listener *Listener) { | ||||
| 	handles := b.handlers[event] | ||||
| 
 | ||||
| 	for i, handle := range handles { | ||||
| 		if handle == listener { | ||||
| 			b.removeListener(event, i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // OffLua removes a Lua function handler for an event. | ||||
| func (b *Bait) OffLua(event string, handler *rt.Closure) { | ||||
| 	handles := b.handlers[event] | ||||
| 
 | ||||
| 	for i, handle := range handles { | ||||
| 		if handle.luaCaller == handler { | ||||
| 			b.removeListener(event, i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Once adds a Go function listener for an event that only runs once. | ||||
| func (b *Bait) Once(event string, handler func(...interface{})) *Listener { | ||||
| 	listener := &Listener{ | ||||
| 		typ: goListener, | ||||
| 		once: true, | ||||
| 		caller: handler, | ||||
| 	} | ||||
| 	b.addListener(event, listener) | ||||
| 
 | ||||
| 	return listener | ||||
| } | ||||
| 
 | ||||
| // OnceLua adds a Lua function listener for an event that only runs once. | ||||
| func (b *Bait) OnceLua(event string, handler *rt.Closure) *Listener { | ||||
| 	listener := &Listener{ | ||||
| 		typ: luaListener, | ||||
| 		once: true, | ||||
| 		luaCaller: handler, | ||||
| 	} | ||||
| 	b.addListener(event, listener) | ||||
| 
 | ||||
| 	return listener | ||||
| } | ||||
| 
 | ||||
| // SetRecoverer sets the function to be executed when a panic occurs in an event. | ||||
| func (b *Bait) SetRecoverer(recoverer Recoverer) { | ||||
| 	b.recoverer = recoverer | ||||
| } | ||||
| 
 | ||||
| func (b *Bait) addListener(event string, listener *Listener) { | ||||
| 	if b.handlers[event] == nil { | ||||
| 		b.handlers[event] = []*Listener{} | ||||
| 	} | ||||
| 
 | ||||
| 	b.handlers[event] = append(b.handlers[event], listener) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| func (b *Bait) removeListener(event string, idx int) { | ||||
| 	b.handlers[event][idx] = b.handlers[event][len(b.handlers[event]) - 1] | ||||
| 
 | ||||
| 	b.handlers[event] = b.handlers[event][:len(b.handlers[event]) - 1] | ||||
| } | ||||
| 
 | ||||
| func (b *Bait) callRecoverer(event string, handler *Listener, err interface{}) { | ||||
| 	if b.recoverer == nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	b.recoverer(event, handler, err) | ||||
| } | ||||
| 
 | ||||
| func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) { | ||||
| 	exports := map[string]util.LuaExport{ | ||||
| 		"catch": util.LuaExport{b.bcatch, 2, false}, | ||||
| 		"catchOnce": util.LuaExport{b.bcatchOnce, 2, false}, | ||||
| 		"throw": util.LuaExport{b.bthrow, 1, true}, | ||||
| 		"release": util.LuaExport{b.brelease, 2, false}, | ||||
| 	} | ||||
| 	mod := rt.NewTable() | ||||
| 	util.SetExports(rtm, mod, exports) | ||||
| @ -89,7 +240,7 @@ func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { | ||||
| 	for i, v := range c.Etc() { | ||||
| 		ifaceSlice[i] = v | ||||
| 	} | ||||
| 	b.Em.Emit(name, ifaceSlice...) | ||||
| 	b.Emit(name, ifaceSlice...) | ||||
| 
 | ||||
| 	return c.Next(), nil | ||||
| } | ||||
| @ -104,9 +255,7 @@ func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	b.Em.On(name, func(args ...interface{}) { | ||||
| 		handleHook(t, c, name, catcher, args...) | ||||
| 	}) | ||||
| 	b.OnLua(name, catcher) | ||||
| 
 | ||||
| 	return c.Next(), nil | ||||
| } | ||||
| @ -121,9 +270,22 @@ func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	b.Em.Once(name, func(args ...interface{}) { | ||||
| 		handleHook(t, c, name, catcher, args...) | ||||
| 	}) | ||||
| 	b.OnceLua(name, catcher) | ||||
| 
 | ||||
| 	return c.Next(), nil | ||||
| } | ||||
| 
 | ||||
| // release(name, catcher) | ||||
| // Removes the `catcher` for the event with `name` | ||||
| // For this to work, `catcher` has to be the same function used to catch | ||||
| // an event, like one saved to a variable. | ||||
| func (b *Bait) brelease(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { | ||||
| 	name, catcher, err := util.HandleStrCallback(t, c) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	b.OffLua(name, catcher) | ||||
| 
 | ||||
| 	return c.Next(), nil | ||||
| } | ||||
|  | ||||
| @ -2,20 +2,20 @@ package commander | ||||
| 
 | ||||
| import ( | ||||
| 	"hilbish/util" | ||||
| 	"hilbish/golibs/bait" | ||||
| 
 | ||||
| 	rt "github.com/arnodel/golua/runtime" | ||||
| 	"github.com/arnodel/golua/lib/packagelib" | ||||
| 	"github.com/chuckpreslar/emission" | ||||
| ) | ||||
| 
 | ||||
| type Commander struct{ | ||||
| 	Events *emission.Emitter | ||||
| 	Events *bait.Bait | ||||
| 	Loader packagelib.Loader | ||||
| } | ||||
| 
 | ||||
| func New() Commander { | ||||
| func New(rtm *rt.Runtime) Commander { | ||||
| 	c := Commander{ | ||||
| 		Events: emission.NewEmitter(), | ||||
| 		Events: bait.New(rtm), | ||||
| 	} | ||||
| 	c.Loader = packagelib.Loader{ | ||||
| 		Load: c.loaderFunc, | ||||
|  | ||||
							
								
								
									
										6
									
								
								job.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								job.go
									
									
									
									
									
								
							| @ -67,7 +67,7 @@ func (j *job) start() error { | ||||
| 	j.pid = proc.Pid | ||||
| 	j.running = true | ||||
| 
 | ||||
| 	hooks.Em.Emit("job.start", rt.UserDataValue(j.ud)) | ||||
| 	hooks.Emit("job.start", rt.UserDataValue(j.ud)) | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| @ -82,7 +82,7 @@ func (j *job) stop() { | ||||
| 
 | ||||
| func (j *job) finish() { | ||||
| 	j.running = false | ||||
| 	hooks.Em.Emit("job.done", rt.UserDataValue(j.ud)) | ||||
| 	hooks.Emit("job.done", rt.UserDataValue(j.ud)) | ||||
| } | ||||
| 
 | ||||
| func (j *job) wait() { | ||||
| @ -236,7 +236,7 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job { | ||||
| 	jb.ud = jobUserData(jb) | ||||
| 
 | ||||
| 	j.jobs[j.latestID] = jb | ||||
| 	hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud)) | ||||
| 	hooks.Emit("job.add", rt.UserDataValue(jb.ud)) | ||||
| 
 | ||||
| 	return jb | ||||
| } | ||||
|  | ||||
							
								
								
									
										22
									
								
								lua.go
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								lua.go
									
									
									
									
									
								
							| @ -32,28 +32,38 @@ func luaInit() { | ||||
| 	lib.LoadLibs(l, fs.Loader) | ||||
| 	lib.LoadLibs(l, terminal.Loader) | ||||
| 
 | ||||
| 	cmds := commander.New() | ||||
| 	cmds := commander.New(l) | ||||
| 	// When a command from Lua is added, register it for use | ||||
| 	cmds.Events.On("commandRegister", func(cmdName string, cmd *rt.Closure) { | ||||
| 	cmds.Events.On("commandRegister", func(args ...interface{}) { | ||||
| 		cmdName := args[0].(string) | ||||
| 		cmd := args[1].(*rt.Closure) | ||||
| 
 | ||||
| 		commands[cmdName] = cmd | ||||
| 	}) | ||||
| 	cmds.Events.On("commandDeregister", func(cmdName string) { | ||||
| 	cmds.Events.On("commandDeregister", func(args ...interface{}) { | ||||
| 		cmdName := args[0].(string) | ||||
| 
 | ||||
| 		delete(commands, cmdName) | ||||
| 	}) | ||||
| 	lib.LoadLibs(l, cmds.Loader) | ||||
| 
 | ||||
| 	hooks = bait.New() | ||||
| 	hooks = bait.New(l) | ||||
| 	hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) { | ||||
| 		fmt.Println("Error in", event, "event:", err) | ||||
| 		hooks.Off(event, handler) | ||||
| 	}) | ||||
| 
 | ||||
| 	lib.LoadLibs(l, hooks.Loader) | ||||
| 
 | ||||
| 	// Add Ctrl-C handler | ||||
| 	hooks.Em.On("signal.sigint", func() { | ||||
| 	hooks.On("signal.sigint", func(...interface{}) { | ||||
| 		if !interactive { | ||||
| 			os.Exit(0) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	lr.rl.RawInputCallback = func(r []rune) { | ||||
| 		hooks.Em.Emit("hilbish.rawInput", string(r)) | ||||
| 		hooks.Emit("hilbish.rawInput", string(r)) | ||||
| 	} | ||||
| 
 | ||||
| 	// Add more paths that Lua can require from | ||||
|  | ||||
							
								
								
									
										10
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								main.go
									
									
									
									
									
								
							| @ -30,7 +30,7 @@ var ( | ||||
| 	userDataDir string | ||||
| 	curuser *user.User | ||||
| 
 | ||||
| 	hooks bait.Bait | ||||
| 	hooks *bait.Bait | ||||
| 	defaultConfPath string | ||||
| 	defaultHistPath string | ||||
| ) | ||||
| @ -138,7 +138,7 @@ func main() { | ||||
| 	} else { | ||||
| 		runConfig(*configflag) | ||||
| 	} | ||||
| 	hooks.Em.Emit("hilbish.init") | ||||
| 	hooks.Emit("hilbish.init") | ||||
| 
 | ||||
| 	if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { | ||||
| 		scanner := bufio.NewScanner(bufio.NewReader(os.Stdin)) | ||||
| @ -177,7 +177,7 @@ input: | ||||
| 
 | ||||
| 		if err == io.EOF { | ||||
| 			// Exit if user presses ^D (ctrl + d) | ||||
| 			hooks.Em.Emit("hilbish.exit") | ||||
| 			hooks.Emit("hilbish.exit") | ||||
| 			break | ||||
| 		} | ||||
| 		if err != nil { | ||||
| @ -196,7 +196,7 @@ input: | ||||
| 		input = strings.TrimSpace(input) | ||||
| 		if len(input) == 0 { | ||||
| 			running = true | ||||
| 			hooks.Em.Emit("command.exit", 0) | ||||
| 			hooks.Emit("command.exit", 0) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| @ -227,7 +227,7 @@ input: | ||||
| } | ||||
| 
 | ||||
| func continuePrompt(prev string) (string, error) { | ||||
| 	hooks.Em.Emit("multiline", nil) | ||||
| 	hooks.Emit("multiline", nil) | ||||
| 	lr.SetPrompt(multilinePrompt) | ||||
| 	cont, err := lr.Read() | ||||
| 	if err != nil { | ||||
|  | ||||
							
								
								
									
										4
									
								
								rl.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								rl.go
									
									
									
									
									
								
							| @ -48,7 +48,7 @@ func newLineReader(prompt string, noHist bool) *lineReader { | ||||
| 			case readline.VimActionPaste: actionStr = "paste" | ||||
| 			case readline.VimActionYank: actionStr = "yank" | ||||
| 		} | ||||
| 		hooks.Em.Emit("hilbish.vimAction", actionStr, args) | ||||
| 		hooks.Emit("hilbish.vimAction", actionStr, args) | ||||
| 	} | ||||
| 	rl.HintText = func(line []rune, pos int) []rune { | ||||
| 		if hinter == nil { | ||||
| @ -179,7 +179,7 @@ func newLineReader(prompt string, noHist bool) *lineReader { | ||||
| } | ||||
| 
 | ||||
| func (lr *lineReader) Read() (string, error) { | ||||
| 	hooks.Em.Emit("command.precmd", nil) | ||||
| 	hooks.Emit("command.precmd", nil) | ||||
| 	s, err := lr.rl.Readline() | ||||
| 	// this is so dumb | ||||
| 	if err == readline.EOF { | ||||
|  | ||||
| @ -15,11 +15,11 @@ func handleSignals() { | ||||
| 
 | ||||
| 	for s := range c { | ||||
| 		switch s { | ||||
| 		case os.Interrupt: hooks.Em.Emit("signal.sigint") | ||||
| 		case os.Interrupt: hooks.Emit("signal.sigint") | ||||
| 		case syscall.SIGTERM: exit(0) | ||||
| 		case syscall.SIGWINCH: hooks.Em.Emit("signal.resize") | ||||
| 		case syscall.SIGUSR1: hooks.Em.Emit("signal.sigusr1") | ||||
| 		case syscall.SIGUSR2: hooks.Em.Emit("signal.sigusr2") | ||||
| 		case syscall.SIGWINCH: hooks.Emit("signal.resize") | ||||
| 		case syscall.SIGUSR1: hooks.Emit("signal.sigusr1") | ||||
| 		case syscall.SIGUSR2: hooks.Emit("signal.sigusr2") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -14,7 +14,7 @@ func handleSignals() { | ||||
| 	for s := range c { | ||||
| 		switch s { | ||||
| 		case os.Interrupt: | ||||
| 			hooks.Em.Emit("signal.sigint") | ||||
| 			hooks.Emit("signal.sigint") | ||||
| 			if !running && interactive { | ||||
| 				lr.ClearInput() | ||||
| 			} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user