Initial commit
This commit is contained in:
23
api/types.go
Normal file
23
api/types.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// Module a plugin that can be initialized
|
||||||
|
type Module interface {
|
||||||
|
Init(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command represents an executable a command
|
||||||
|
type Command interface {
|
||||||
|
Name() string
|
||||||
|
Usage() string
|
||||||
|
ShortDesc() string
|
||||||
|
LongDesc() string
|
||||||
|
Exec(context.Context, []string) (context.Context, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands a plugin that contains one or more command
|
||||||
|
type Commands interface {
|
||||||
|
Module
|
||||||
|
Registry() map[string]Command
|
||||||
|
}
|
||||||
39
api/utils.go
Normal file
39
api/utils.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PluginsDir = "./plugins"
|
||||||
|
CmdSymbolName = "Commands"
|
||||||
|
DefaultPrompt = "gosh>"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStdout(ctx context.Context) io.Writer {
|
||||||
|
var out io.Writer = os.Stdout
|
||||||
|
if ctx == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if outVal := ctx.Value("gosh.stdout"); outVal != nil {
|
||||||
|
if stdout, ok := outVal.(io.Writer); ok {
|
||||||
|
out = stdout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPrompt(ctx context.Context) string {
|
||||||
|
prompt := DefaultPrompt
|
||||||
|
if ctx == nil {
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
if promptVal := ctx.Value("gosh.prompt"); promptVal != nil {
|
||||||
|
if p, ok := promptVal.(string); ok {
|
||||||
|
prompt = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
168
plugins/syscmd.go
Normal file
168
plugins/syscmd.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vladimirvivien/gosh/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helpCmd represents the `help` command
|
||||||
|
// which prints out help information about other commands
|
||||||
|
type helpCmd string
|
||||||
|
|
||||||
|
func (h helpCmd) Name() string { return string(h) }
|
||||||
|
func (h helpCmd) Usage() string { return fmt.Sprintf("%s or %s <command-name>", h.Name(), h.Name()) }
|
||||||
|
func (h helpCmd) LongDesc() string { return "" }
|
||||||
|
func (h helpCmd) ShortDesc() string {
|
||||||
|
return `prints help information for other commands.`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h helpCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
return ctx, errors.New("nil context")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := api.GetStdout(ctx)
|
||||||
|
|
||||||
|
cmdsVal := ctx.Value("gosh.commands")
|
||||||
|
if cmdsVal == nil {
|
||||||
|
return ctx, errors.New("nil context")
|
||||||
|
}
|
||||||
|
|
||||||
|
commands, ok := cmdsVal.(map[string]api.Command)
|
||||||
|
if !ok {
|
||||||
|
return ctx, errors.New("command map type mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdNameParam string
|
||||||
|
if len(args) > 1 {
|
||||||
|
cmdNameParam = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// print help for a specified command
|
||||||
|
if cmdNameParam != "" {
|
||||||
|
cmd, found := commands[cmdNameParam]
|
||||||
|
if !found {
|
||||||
|
str := fmt.Sprintf("command %s not found", cmdNameParam)
|
||||||
|
return ctx, errors.New(str)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "\n%s\n", cmdNameParam)
|
||||||
|
if cmd.Usage() != "" {
|
||||||
|
fmt.Fprintf(out, " Usage: %s\n", cmd.Usage())
|
||||||
|
}
|
||||||
|
if cmd.ShortDesc() != "" {
|
||||||
|
fmt.Fprintf(out, " %s\n\n", cmd.ShortDesc())
|
||||||
|
}
|
||||||
|
if cmd.LongDesc() != "" {
|
||||||
|
fmt.Fprintf(out, "%s\n\n", cmd.LongDesc())
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(out, "\n%s: %s\n", h.Name(), h.ShortDesc())
|
||||||
|
fmt.Fprintln(out, "\nAvailable commands")
|
||||||
|
fmt.Fprintln(out, "------------------")
|
||||||
|
for cmdName, cmd := range commands {
|
||||||
|
fmt.Fprintf(out, "%12s:\t%s\n", cmdName, cmd.ShortDesc())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "\nUse \"help <command-name>\" for detail about the specified command\n")
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitCmd implements a command to exit the shell
|
||||||
|
type exitCmd string
|
||||||
|
|
||||||
|
func (c exitCmd) Name() string { return string(c) }
|
||||||
|
func (c exitCmd) Usage() string { return "exit" }
|
||||||
|
func (c exitCmd) LongDesc() string { return "" }
|
||||||
|
func (c exitCmd) ShortDesc() string {
|
||||||
|
return `exits the interactive shell immediately`
|
||||||
|
}
|
||||||
|
func (c exitCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
out := api.GetStdout(ctx)
|
||||||
|
fmt.Fprintln(out, "exiting...")
|
||||||
|
os.Exit(0)
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptCmd a command that can change the prompt value
|
||||||
|
type promptCmd string
|
||||||
|
|
||||||
|
func (c promptCmd) Name() string { return string(c) }
|
||||||
|
func (c promptCmd) Usage() string { return "prompt <new-prompt>" }
|
||||||
|
func (c promptCmd) LongDesc() string { return "" }
|
||||||
|
func (c promptCmd) ShortDesc() string {
|
||||||
|
return `sets a new shell prompt`
|
||||||
|
}
|
||||||
|
func (c promptCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return ctx, errors.New("unable to set prompt, see usage")
|
||||||
|
}
|
||||||
|
return context.WithValue(ctx, "gosh.prompt", args[1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysinfoCmd implements a command that returns system information
|
||||||
|
type sysinfoCmd string
|
||||||
|
|
||||||
|
func (c sysinfoCmd) Name() string { return string(c) }
|
||||||
|
func (c sysinfoCmd) Usage() string { return c.Name() }
|
||||||
|
func (c sysinfoCmd) LongDesc() string { return "" }
|
||||||
|
func (c sysinfoCmd) ShortDesc() string {
|
||||||
|
return `sets a new shell prompt`
|
||||||
|
}
|
||||||
|
func (c sysinfoCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
out := api.GetStdout(ctx)
|
||||||
|
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
memStats := new(runtime.MemStats)
|
||||||
|
runtime.ReadMemStats(memStats)
|
||||||
|
info := []struct{ name, value string }{
|
||||||
|
{"arc", runtime.GOARCH},
|
||||||
|
{"os", runtime.GOOS},
|
||||||
|
{"cpus", strconv.Itoa(runtime.NumCPU())},
|
||||||
|
{"mem", strconv.FormatUint(memStats.Sys, 10)},
|
||||||
|
{"hostname", hostname},
|
||||||
|
{"pagesize", strconv.Itoa(os.Getpagesize())},
|
||||||
|
{"groupid", strconv.Itoa(os.Getgid())},
|
||||||
|
{"userid", strconv.Itoa(os.Geteuid())},
|
||||||
|
{"pid", strconv.Itoa(os.Getpid())},
|
||||||
|
{"exec", exe},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(out, "\nSystem Info")
|
||||||
|
fmt.Fprint(out, "\n-----------")
|
||||||
|
for _, k := range info {
|
||||||
|
fmt.Fprintf(out, "\n%12s:\t%s", k.name, k.value)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, "\n")
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysCommands represents a collection of commands supported by this
|
||||||
|
// command module.
|
||||||
|
type sysCommands struct {
|
||||||
|
stdout io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sysCommands) Init(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *sysCommands) Registry() map[string]api.Command {
|
||||||
|
return map[string]api.Command{
|
||||||
|
"help": helpCmd("help"),
|
||||||
|
"exit": exitCmd("exit"),
|
||||||
|
"prompt": promptCmd("prompt"),
|
||||||
|
"sys": sysinfoCmd("sys"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin entry point
|
||||||
|
var Commands sysCommands
|
||||||
51
plugins/testcmd.go
Normal file
51
plugins/testcmd.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/vladimirvivien/gosh/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type helloCmd string
|
||||||
|
|
||||||
|
func (t helloCmd) Name() string { return string(t) }
|
||||||
|
func (t helloCmd) Usage() string { return `hello` }
|
||||||
|
func (t helloCmd) ShortDesc() string { return `prints greeting "hello there"` }
|
||||||
|
func (t helloCmd) LongDesc() string { return t.ShortDesc() }
|
||||||
|
func (t helloCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
out := ctx.Value("gosh.stdout").(io.Writer)
|
||||||
|
fmt.Fprintln(out, "hello there")
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type goodbyeCmd string
|
||||||
|
|
||||||
|
func (t goodbyeCmd) Name() string { return string(t) }
|
||||||
|
func (t goodbyeCmd) Usage() string { return t.Name() }
|
||||||
|
func (t goodbyeCmd) ShortDesc() string { return `prints message "bye bye"` }
|
||||||
|
func (t goodbyeCmd) LongDesc() string { return t.ShortDesc() }
|
||||||
|
func (t goodbyeCmd) Exec(ctx context.Context, args []string) (context.Context, error) {
|
||||||
|
out := ctx.Value("gosh.stdout").(io.Writer)
|
||||||
|
fmt.Fprintln(out, "bye bye")
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// command module
|
||||||
|
type testCmds struct{}
|
||||||
|
|
||||||
|
func (t *testCmds) Init(ctx context.Context) error {
|
||||||
|
out := ctx.Value("gosh.stdout").(io.Writer)
|
||||||
|
fmt.Fprintln(out, "test module loaded OK")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testCmds) Registry() map[string]api.Command {
|
||||||
|
return map[string]api.Command{
|
||||||
|
"hello": helloCmd("hello"),
|
||||||
|
"goodbye": goodbyeCmd("goodbye"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Commands testCmds
|
||||||
7
plugins/timecmd.go
Normal file
7
plugins/timecmd.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("vim-go")
|
||||||
|
}
|
||||||
190
shell/gosh.go
Normal file
190
shell/gosh.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"plugin"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vladimirvivien/gosh/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
reCmd = regexp.MustCompile(`\S+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Goshell struct {
|
||||||
|
ctx context.Context
|
||||||
|
pluginsDir string
|
||||||
|
commands map[string]api.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Goshell {
|
||||||
|
return &Goshell{
|
||||||
|
pluginsDir: api.PluginsDir,
|
||||||
|
commands: make(map[string]api.Command),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gosh *Goshell) Init(ctx context.Context) error {
|
||||||
|
gosh.ctx = ctx
|
||||||
|
gosh.printSplash()
|
||||||
|
return gosh.loadCommands()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gosh *Goshell) loadCommands() error {
|
||||||
|
if _, err := os.Stat(gosh.pluginsDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins, err := listFiles(gosh.pluginsDir, `.*_command.so`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cmdPlugin := range plugins {
|
||||||
|
plug, err := plugin.Open(path.Join(gosh.pluginsDir, cmdPlugin.Name()))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to open plugin %s: %v\n", cmdPlugin.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmdSymbol, err := plug.Lookup(api.CmdSymbolName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("plugin %s does not export symbol \"%s\"\n",
|
||||||
|
cmdPlugin.Name(), api.CmdSymbolName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
commands, ok := cmdSymbol.(api.Commands)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("Symbol %s (from %s) does not implement Commands interface\n",
|
||||||
|
api.CmdSymbolName, cmdPlugin.Name())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := commands.Init(gosh.ctx); err != nil {
|
||||||
|
fmt.Printf("%s initialization failed: %v\n", cmdPlugin.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for name, cmd := range commands.Registry() {
|
||||||
|
gosh.commands[name] = cmd
|
||||||
|
}
|
||||||
|
gosh.ctx = context.WithValue(gosh.ctx, "gosh.commands", gosh.commands)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO delegate splash to a plugin
|
||||||
|
func (gosh *Goshell) printSplash() {
|
||||||
|
fmt.Println(`
|
||||||
|
888
|
||||||
|
888
|
||||||
|
888
|
||||||
|
.d88b. .d88b. .d8888b 88888b.
|
||||||
|
d88P"88bd88""88b88K 888 "88b
|
||||||
|
888 888888 888"Y8888b.888 888
|
||||||
|
Y88b 888Y88..88P X88888 888
|
||||||
|
"Y88888 "Y88P" 88888P'888 888
|
||||||
|
888
|
||||||
|
Y8b d88P
|
||||||
|
"Y88P"
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gosh *Goshell) handle(ctx context.Context, cmdLine string) (context.Context, error) {
|
||||||
|
line := strings.TrimSpace(cmdLine)
|
||||||
|
if line == "" {
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
args := reCmd.FindAllString(line, -1)
|
||||||
|
if args != nil {
|
||||||
|
cmdName := args[0]
|
||||||
|
cmd, ok := gosh.commands[cmdName]
|
||||||
|
if !ok {
|
||||||
|
return ctx, errors.New(fmt.Sprintf("command not found: %s", cmdName))
|
||||||
|
}
|
||||||
|
return cmd.Exec(ctx, args)
|
||||||
|
}
|
||||||
|
return ctx, errors.New(fmt.Sprintf("unable to parse command line: %s", line))
|
||||||
|
}
|
||||||
|
|
||||||
|
func listFiles(dir, pattern string) ([]os.FileInfo, error) {
|
||||||
|
files, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredFiles := []os.FileInfo{}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matched, err := regexp.MatchString(pattern, file.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
filteredFiles = append(filteredFiles, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "gosh.prompt", api.DefaultPrompt)
|
||||||
|
ctx = context.WithValue(ctx, "gosh.stdout", os.Stdout)
|
||||||
|
ctx = context.WithValue(ctx, "gosh.stderr", os.Stderr)
|
||||||
|
ctx = context.WithValue(ctx, "gosh.stdin", os.Stdin)
|
||||||
|
|
||||||
|
shell := New()
|
||||||
|
if err := shell.Init(ctx); err != nil {
|
||||||
|
fmt.Print("\n\nfailed to initialize:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt for help
|
||||||
|
cmdCount := len(shell.commands)
|
||||||
|
if cmdCount > 0 {
|
||||||
|
if _, ok := shell.commands["help"]; ok {
|
||||||
|
fmt.Printf("\nLoaded %d command(s)...", cmdCount)
|
||||||
|
fmt.Println("\nType help for available commands")
|
||||||
|
fmt.Print("\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// wait
|
||||||
|
// TODO: sig handling
|
||||||
|
select {}
|
||||||
|
}
|
||||||
73
shell/gosh_test.go
Normal file
73
shell/gosh_test.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testPluginsDir = "../plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShellNew(t *testing.T) {
|
||||||
|
shell := New()
|
||||||
|
if shell.pluginsDir != pluginsDir {
|
||||||
|
t.Error("pluginsDir not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShellInit(t *testing.T) {
|
||||||
|
shell := New()
|
||||||
|
shell.pluginsDir = testPluginsDir
|
||||||
|
ctx := context.WithValue(context.TODO(), "gosh.stdout", os.Stdout)
|
||||||
|
if err := shell.Init(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(shell.commands) <= 0 {
|
||||||
|
t.Error("failed to load plugins from", testPluginsDir)
|
||||||
|
}
|
||||||
|
if _, ok := shell.commands["hello"]; !ok {
|
||||||
|
t.Error("missing 'hello' command from test module")
|
||||||
|
}
|
||||||
|
if _, ok := shell.commands["goodbye"]; !ok {
|
||||||
|
t.Error("missing 'goodbye' command from test module")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShellHandle(t *testing.T) {
|
||||||
|
shell := New()
|
||||||
|
shell.pluginsDir = testPluginsDir
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.TODO(), "gosh.stdout", os.Stdout)
|
||||||
|
if err := shell.Init(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helloOut := bytes.NewBufferString("")
|
||||||
|
shell.ctx = context.WithValue(context.TODO(), "gosh.stdout", helloOut)
|
||||||
|
if err := shell.handle("testhello"); err == nil {
|
||||||
|
t.Error("this test should have failed with command not found")
|
||||||
|
}
|
||||||
|
if err := shell.handle("hello"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
printedOut := strings.TrimSpace(helloOut.String())
|
||||||
|
if printedOut != "hello there" {
|
||||||
|
t.Error("did not get expected output from testcmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
byeOut := bytes.NewBufferString("")
|
||||||
|
shell.ctx = context.WithValue(context.TODO(), "gosh.stdout", byeOut)
|
||||||
|
if err := shell.handle("goodbye"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
printedOut = strings.TrimSpace(byeOut.String())
|
||||||
|
if printedOut != "bye bye" {
|
||||||
|
t.Error("did not get expected output from testcmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user