83
gosh.go
83
gosh.go
@@ -5,12 +5,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
"plugin"
|
"plugin"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/vladimirvivien/gosh/api"
|
"github.com/vladimirvivien/gosh/api"
|
||||||
)
|
)
|
||||||
@@ -23,15 +26,19 @@ type Goshell struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
pluginsDir string
|
pluginsDir string
|
||||||
commands map[string]api.Command
|
commands map[string]api.Command
|
||||||
|
closed chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New returns a new shell
|
||||||
func New() *Goshell {
|
func New() *Goshell {
|
||||||
return &Goshell{
|
return &Goshell{
|
||||||
pluginsDir: api.PluginsDir,
|
pluginsDir: api.PluginsDir,
|
||||||
commands: make(map[string]api.Command),
|
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 {
|
func (gosh *Goshell) Init(ctx context.Context) error {
|
||||||
gosh.ctx = ctx
|
gosh.ctx = ctx
|
||||||
gosh.printSplash()
|
gosh.printSplash()
|
||||||
@@ -96,6 +103,49 @@ 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}(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) {
|
func (gosh *Goshell) handle(ctx context.Context, cmdLine string) (context.Context, error) {
|
||||||
line := strings.TrimSpace(cmdLine)
|
line := strings.TrimSpace(cmdLine)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
@@ -162,29 +212,14 @@ func main() {
|
|||||||
fmt.Print("\n\nNo commands found")
|
fmt.Print("\n\nNo commands found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// shell loop
|
go shell.Open(bufio.NewReader(os.Stdin))
|
||||||
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)
|
|
||||||
|
|
||||||
// wait
|
sigs := make(chan os.Signal)
|
||||||
// TODO: sig handling
|
signal.Notify(sigs, syscall.SIGINT)
|
||||||
select {}
|
select {
|
||||||
|
case <-sigs:
|
||||||
|
cancel()
|
||||||
|
<-shell.Closed()
|
||||||
|
case <-shell.Closed():
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user