From 3c0d050d0a70ab941df7de1da30ae845274e2221 Mon Sep 17 00:00:00 2001 From: Gavin Date: Sat, 12 Oct 2019 22:10:00 -0400 Subject: [PATCH 1/3] cancel the context on SIGINT signal --- gosh.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gosh.go b/gosh.go index 616f488..97ba9ee 100644 --- a/gosh.go +++ b/gosh.go @@ -7,10 +7,12 @@ import ( "fmt" "io/ioutil" "os" + "os/signal" "path" "plugin" "regexp" "strings" + "syscall" "github.com/vladimirvivien/gosh/api" ) @@ -184,7 +186,10 @@ func main() { } }(shell.ctx, shell) - // wait - // TODO: sig handling - select {} + sigs := make(chan os.Signal) + signal.Notify(sigs, syscall.SIGINT) + select { + case <-sigs: + cancel() + } } From a71dc98158d62c95de99d080d43ce6a6c75f8a5b Mon Sep 17 00:00:00 2001 From: Gavin Date: Tue, 15 Oct 2019 02:58:13 -0400 Subject: [PATCH 2/3] better cancelation handling, allowing the shell to gracefully close --- gosh.go | 68 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/gosh.go b/gosh.go index 97ba9ee..84c7b6f 100644 --- a/gosh.go +++ b/gosh.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io" "io/ioutil" "os" "os/signal" @@ -25,15 +26,19 @@ type Goshell struct { ctx context.Context pluginsDir string commands map[string]api.Command + closed chan struct{} } +// New returns a new shell func New() *Goshell { return &Goshell{ pluginsDir: api.PluginsDir, commands: make(map[string]api.Command), + closed: make(chan struct{}), } } +// Init initializes the shell with the given context func (gosh *Goshell) Init(ctx context.Context) error { gosh.ctx = ctx gosh.printSplash() @@ -98,6 +103,45 @@ Y8b d88P `) } +// Open opens the shell for the given reader +func (gosh *Goshell) Open(r *bufio.Reader) { + loopCtx := gosh.ctx + line := make(chan string) + for { + // start a goroutine to get input from the user + go func(ctx context.Context, input chan<- string) { + // TODO: future enhancement is to capture input key by key + // to give command granular notification of key events. + // This could be used to implement command autocompletion. + fmt.Fprintf(ctx.Value("gosh.stdout").(io.Writer), "%s ", api.GetPrompt(loopCtx)) + line, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(ctx.Value("gosh.stderr").(io.Writer), "%v\n", err) + return + } + input <- line + }(loopCtx, line) + + // wait for input or cancel + select { + case <-gosh.ctx.Done(): + close(gosh.closed) + return + case input := <-line: + var err error + loopCtx, err = gosh.handle(loopCtx, input) + if err != nil { + fmt.Fprintf(loopCtx.Value("gosh.stderr").(io.Writer), "%v\n", err) + } + } + } +} + +// Closed returns a channel that closes when the shell has closed +func (gosh *Goshell) Closed() <-chan struct{} { + return gosh.closed +} + func (gosh *Goshell) handle(ctx context.Context, cmdLine string) (context.Context, error) { line := strings.TrimSpace(cmdLine) if line == "" { @@ -164,32 +208,14 @@ func main() { fmt.Print("\n\nNo commands found") } - // shell loop - go func(shellCtx context.Context, shell *Goshell) { - lineReader := bufio.NewReader(os.Stdin) - loopCtx := shellCtx - for { - fmt.Printf("%s ", api.GetPrompt(loopCtx)) - line, err := lineReader.ReadString('\n') - if err != nil { - fmt.Println(err) - return - } - // TODO: future enhancement is to capture input key by key - // to give command granular notification of key events. - // This could be used to implement command autocompletion. - c, err := shell.handle(loopCtx, line) - loopCtx = c - if err != nil { - fmt.Printf("%v\n", err) - } - } - }(shell.ctx, shell) + go shell.Open(bufio.NewReader(os.Stdin)) sigs := make(chan os.Signal) signal.Notify(sigs, syscall.SIGINT) select { case <-sigs: cancel() + <-shell.Closed() + case <-shell.Closed(): } } From a22fdcd72d83ef64ebc352d14efddd575fff4692 Mon Sep 17 00:00:00 2001 From: Gavin Date: Tue, 15 Oct 2019 03:04:41 -0400 Subject: [PATCH 3/3] remove bug that would cause input reading to stop if an error was encountered --- gosh.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/gosh.go b/gosh.go index 84c7b6f..953e113 100644 --- a/gosh.go +++ b/gosh.go @@ -110,16 +110,20 @@ func (gosh *Goshell) Open(r *bufio.Reader) { for { // start a goroutine to get input from the user go func(ctx context.Context, input chan<- string) { - // TODO: future enhancement is to capture input key by key - // to give command granular notification of key events. - // This could be used to implement command autocompletion. - fmt.Fprintf(ctx.Value("gosh.stdout").(io.Writer), "%s ", api.GetPrompt(loopCtx)) - line, err := r.ReadString('\n') - if err != nil { - fmt.Fprintf(ctx.Value("gosh.stderr").(io.Writer), "%v\n", err) + for { + // TODO: future enhancement is to capture input key by key + // to give command granular notification of key events. + // This could be used to implement command autocompletion. + fmt.Fprintf(ctx.Value("gosh.stdout").(io.Writer), "%s ", api.GetPrompt(loopCtx)) + line, err := r.ReadString('\n') + if err != nil { + fmt.Fprintf(ctx.Value("gosh.stderr").(io.Writer), "%v\n", err) + continue + } + + input <- line return } - input <- line }(loopCtx, line) // wait for input or cancel