From 4b701e48cc6fe3747122432557f26c9e000b937d Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 30 Jul 2021 01:58:45 -0500 Subject: [PATCH] Switch to newly extracted "hush" shell --- cmd/sh/builtins.go | 355 ---------------------------------- cmd/sh/call.go | 194 ------------------- cmd/sh/command.go | 251 ------------------------ cmd/sh/completions.go | 114 ----------- cmd/sh/cr_stream.go | 31 --- cmd/sh/err_exit.go | 11 -- cmd/sh/history.go | 92 --------- cmd/sh/js_builtins.go | 73 ------- cmd/sh/main.go | 40 +--- cmd/sh/prompt.go | 68 ------- cmd/sh/rune_reader.go | 29 --- cmd/sh/table.go | 70 ------- cmd/sh/terminal.go | 432 ------------------------------------------ cmd/sh/tty_js.go | 9 - cmd/sh/tty_other.go | 30 --- go.mod | 4 +- go.sum | 44 +++-- 17 files changed, 32 insertions(+), 1815 deletions(-) delete mode 100644 cmd/sh/builtins.go delete mode 100644 cmd/sh/call.go delete mode 100644 cmd/sh/command.go delete mode 100644 cmd/sh/completions.go delete mode 100644 cmd/sh/cr_stream.go delete mode 100644 cmd/sh/err_exit.go delete mode 100644 cmd/sh/history.go delete mode 100644 cmd/sh/js_builtins.go delete mode 100644 cmd/sh/prompt.go delete mode 100644 cmd/sh/rune_reader.go delete mode 100644 cmd/sh/table.go delete mode 100644 cmd/sh/terminal.go delete mode 100644 cmd/sh/tty_js.go delete mode 100644 cmd/sh/tty_other.go diff --git a/cmd/sh/builtins.go b/cmd/sh/builtins.go deleted file mode 100644 index c77c424..0000000 --- a/cmd/sh/builtins.go +++ /dev/null @@ -1,355 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path" - "strconv" - "strings" - "syscall" - "time" - - "github.com/fatih/color" - "github.com/johnstarich/go-wasm/internal/console" - "github.com/johnstarich/go/datasize" - "github.com/pkg/errors" -) - -type builtinFunc func(term console.Console, args ...string) error - -var ( - builtins = map[string]builtinFunc{} -) - -func init() { - for k, v := range map[string]builtinFunc{ - "cat": cat, - "cd": cd, - "chmod": chmod, - "clear": clear, - "echo": echo, - "env": env, - "exit": exit, - "ls": ls, - "mkdir": mkdir, - "mv": mv, - "pwd": pwd, - "rm": rm, - "rmdir": rmdir, - "touch": touch, - "which": which, - } { - builtins[k] = v - } -} - -func echo(term console.Console, args ...string) error { - fmt.Fprintln(term.Stdout(), strings.Join(args, " ")) - return nil -} - -func pwd(term console.Console, args ...string) error { - path, err := os.Getwd() - if err != nil { - return err - } - fmt.Fprintln(term.Stdout(), path) - return nil -} - -func ls(term console.Console, args ...string) error { - set := flag.NewFlagSet("ls", flag.ContinueOnError) - longForm := set.Bool("l", false, "Long format") - err := set.Parse(args) - if err != nil { - if err == flag.ErrHelp { - return nil - } - return err - } - args = set.Args() - if len(args) == 0 { - args = []string{"."} - } - if len(args) == 1 { - return printFileNames(term, args[0], *longForm) - } - for _, f := range args { - fmt.Fprintln(term.Stdout(), f+":") - err := printFileNames(term, f, *longForm) - if err != nil { - return err - } - fmt.Fprintln(term.Stdout()) - } - return nil -} - -func printFileNames(term console.Console, dir string, longForm bool) error { - infos, err := ioutil.ReadDir(dir) - if err != nil { - return err - } - - if !longForm { - for _, info := range infos { - fmt.Fprintln(term.Stdout(), info.Name()) - } - return nil - } - - var t table - t.Align(leftAlign, rightAlign) - for _, info := range infos { - value, units := formatBytes(datasize.Bytes(info.Size())) - t.Add(info.Mode(), value, units, info.ModTime().Format(time.Stamp), info.Name()) - } - fmt.Fprint(term.Stdout(), t) - return nil -} - -func formatBytes(b datasize.Size) (string, string) { - value, unit := b.FormatSI() - return strings.TrimSuffix(fmt.Sprintf("%.1f", value), ".0"), unit -} - -func cd(term console.Console, args ...string) error { - switch len(args) { - case 0: - dir, err := os.UserHomeDir() - if err != nil { - return err - } - args = []string{dir} - fallthrough - case 1: - dir := args[0] - info, err := os.Stat(dir) - if err != nil { - return err - } - if !info.IsDir() { - return errors.Errorf("Not a directory: %s", dir) - } - return os.Chdir(dir) - default: - return errors.New("Too many args") - } -} - -func mkdir(term console.Console, args ...string) error { - switch len(args) { - case 0: - return errors.New("Must provide a path to create a directory") - default: - for _, dir := range args { - err := os.Mkdir(dir, 0755) - if err != nil { - return err - } - } - return nil - } -} - -func cat(term console.Console, args ...string) error { - if len(args) == 0 { - _, err := io.Copy(term.Stdout(), getConsoleStdin(term)) - return err - } - - for _, path := range args { - if info, err := os.Stat(path); err == nil && info.IsDir() { - return errors.Errorf("%s: Is a directory", path) - } - f, err := os.Open(path) - if err != nil { - return err - } - _, err = io.Copy(term.Stdout(), f) - if err != nil { - return err - } - } - return nil -} - -func mv(term console.Console, args ...string) error { - switch len(args) { - case 0, 1: - return errors.New("Not enough args") - case 2: - src := args[0] - dest := args[1] - if strings.HasSuffix(dest, "/") { - dest += path.Base(src) - } - return os.Rename(src, dest) - default: - return errors.New("Too many args") - } -} - -func rm(term console.Console, args ...string) error { - set := flag.NewFlagSet("rm", flag.ContinueOnError) - recursive := set.Bool("r", false, "Remove recursively") - if err := set.Parse(args); err != nil { - return err - } - - if set.NArg() == 0 { - return errors.New("Not enough args") - } - - rmFunc := os.RemoveAll - if !*recursive { - rmFunc = func(path string) error { - info, err := os.Stat(path) - if err != nil { - return err - } - if info.IsDir() { - return &os.PathError{Path: path, Op: "remove", Err: syscall.EISDIR} - } - return os.Remove(path) - } - } - for _, f := range set.Args() { - err := rmFunc(f) - if err != nil { - return err - } - } - return nil -} - -func rmdir(term console.Console, args ...string) error { - if len(args) == 0 { - return errors.New("Not enough args") - } - for _, path := range args { - info, err := os.Stat(path) - if err != nil { - return err - } - if !info.IsDir() { - return &os.PathError{Path: path, Op: "remove", Err: syscall.ENOTDIR} - } - err = os.Remove(path) - if err != nil { - return err - } - } - return nil -} - -func touch(term console.Console, args ...string) error { - if len(args) == 0 { - return errors.New("Not enough args") - } - now := time.Now() - for _, path := range args { - err := os.Chtimes(path, now, now) - if err != nil && !os.IsNotExist(err) { - return err - } - if os.IsNotExist(err) { - f, err := os.Create(path) - if err != nil { - return err - } - err = f.Close() - if err != nil { - return err - } - } - } - return nil -} - -func which(term console.Console, args ...string) error { - if len(args) == 0 { - return errors.New("Not enough args") - } - for _, arg := range args { - path, err := exec.LookPath(arg) - if err != nil { - return err - } - fmt.Fprintln(term.Stdout(), path) - } - return nil -} - -func clear(term console.Console, args ...string) error { - term.(*terminal).Clear() - return nil -} - -func exit(term console.Console, args ...string) error { - if len(args) == 0 { - return &ExitErr{Code: 0} - } - - exitCode, err := strconv.ParseInt(args[0], 10, 64) - if err != nil { - return err - } - fmt.Fprintf(term.Stderr(), color.RedString("Exited with code %d\n"), exitCode) - return &ExitErr{Code: int(exitCode)} -} - -func env(term console.Console, args ...string) error { - var kv []string - const equals = '=' - for i, arg := range args { - if !strings.ContainsRune(arg, equals) { - args = args[i:] - break - } - kv = append(kv, arg) - } - - if len(args) == 0 { - for _, e := range os.Environ() { - fmt.Fprintln(term.Stdout(), e) - } - return nil - } - - return runWithEnv(term, kv, args...) -} - -func splitKeyValue(kv string) (key, value string) { - const equals = "=" - tokens := strings.SplitN(kv, equals, 2) - if len(tokens) < 2 { - return strings.Join(tokens, equals), "" - } - return tokens[0], strings.Join(tokens[1:], equals) -} - -func runWithEnv(term console.Console, env []string, args ...string) error { - cmd := exec.Command(args[0], args[1:]...) // nolint:gosec // Running any given process args is the whole point, so this isn't a security issue. - cmd.Stdout = term.Stdout() - cmd.Stderr = term.Stderr() - cmd.Env = append(os.Environ(), env...) - return runCmd(cmd, cmdOptions{}) -} - -func chmod(term console.Console, args ...string) error { - if len(args) < 2 { - return errors.New("Not enough args") - } - - perm, err := strconv.ParseInt(args[0], 8, 12) // parse octal permission - if err != nil { - return err - } - file := args[1] - return os.Chmod(file, os.FileMode(perm)) -} diff --git a/cmd/sh/call.go b/cmd/sh/call.go deleted file mode 100644 index b63a340..0000000 --- a/cmd/sh/call.go +++ /dev/null @@ -1,194 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - "strconv" - "strings" - - "github.com/johnstarich/go-wasm/internal/console" - "github.com/pkg/errors" - "mvdan.cc/sh/v3/syntax" -) - -func runCallExpr(term console.Console, stmt *syntax.Stmt, node *syntax.CallExpr, isPipe bool) error { - var env []string - for _, assign := range node.Assigns { - key := assign.Name.Value - value, err := evalWord(assign.Value.Parts) - if err != nil { - return err - } - env = append(env, fmt.Sprintf("%s=%s", key, value)) - } - - var args []string - for _, arg := range node.Args { - argStr, err := evalWord(arg.Parts) - if err != nil { - return err - } - args = append(args, argStr) - } - if len(args) == 0 { - return errors.New("Setting variables only is not supported") - } - - commandName, args := args[0], args[1:] - cmd := exec.Command(commandName, args...) - cmd.Env = append(os.Environ(), env...) - cmd.Stdin = getConsoleStdin(term) - cmd.Stdout = term.Stdout() - cmd.Stderr = term.Stderr() - - for _, redir := range stmt.Redirs { - err := applyRedirection(cmd, redir) - if err != nil { - return err - } - } - - err := runCmd(cmd, cmdOptions{ - Background: stmt.Background, - Pipe: isPipe, - }) - err = exitErrFromCmd(err, stmt.Negated) - return err - -} - -func applyRedirection(cmd *exec.Cmd, redir *syntax.Redirect) error { - var redirectPtr string - var err error - switch redir.Op { - case syntax.Hdoc, // << - syntax.DashHdoc: // <<- - if redir.Hdoc == nil { - var word string - if redir.Word != nil { - word, _ = evalWord(redir.Word.Parts) - word = ": " + word - } - return errors.New("Invalid heredoc" + word) - } - redirectPtr, err = evalWord(redir.Hdoc.Parts) - case syntax.WordHdoc: // <<< - redirectPtr, err = evalWord(redir.Word.Parts) - default: - redirectPtr, err = evalWord(redir.Word.Parts) - } - if err != nil { - return err - } - - var fd int - switch redir.Op { - case syntax.RdrOut, // > - syntax.AppOut, // >> - syntax.RdrAll, // &> - syntax.AppAll: // &>> - fd = 1 - default: - fd = 0 - } - if redir.N != nil { - fdStr := redir.N.Value - parsedFD, err := strconv.ParseUint(fdStr, 10, 64) - if err != nil { - return err - } - fd = int(parsedFD) - } - - switch redir.Op { - case syntax.RdrOut, // > - syntax.AppOut, // >> - syntax.RdrAll, // &> - syntax.AppAll: // &>> - if fd == 0 { - return errors.New("Can't redirect stdin to an output file") - } - - flag := os.O_WRONLY | os.O_CREATE - if redir.Op == syntax.AppOut || redir.Op == syntax.AppAll { - flag |= os.O_APPEND - } else { - flag |= os.O_TRUNC - } - file, err := os.OpenFile(redirectPtr, flag, 0700) - if err != nil { - return err - } - - switch fd { - case 1: - cmd.Stdout = file - case 2: - cmd.Stderr = file - default: - cmd.ExtraFiles = append(cmd.ExtraFiles, file) - } - case syntax.RdrIn: // < - if fd != 0 { - return errors.New("Can't redirect non-stdin to an input file") - } - - file, err := os.OpenFile(redirectPtr, os.O_RDONLY, 0) - if err != nil { - return err - } - - cmd.Stdin = file - case syntax.Hdoc, // << - syntax.DashHdoc, // <<- - syntax.WordHdoc: // <<< - file := strings.NewReader(redirectPtr) - if fd != 0 { - return errors.New("Can't redirect non-stdin to an input file") - } - cmd.Stdin = file - default: - return errors.Errorf("File redirect of type %q are not supported", redir.Op.String()) - } - return nil -} - -func exitErrFromCmd(err error, negated bool) error { - code := exitCodeFromCmd(err, negated) - if code == 0 { - return nil - } - if err != nil { - return err - } - return errors.New("Negated return code") -} - -// exitCodeFromCmd tries to produce an exit code for the given error. -// 0 for success, non-0 for failure. -// If negated is true, the success result is flipped. -func exitCodeFromCmd(err error, negated bool) int { - return negateExitCode(exitCodeFromErr(err), negated) -} - -func exitCodeFromErr(err error) int { - if err == nil { - return 0 - } - exitErr, ok := err.(*exec.ExitError) - if !ok { - return 1 - } - return exitErr.ExitCode() -} - -func negateExitCode(code int, negated bool) int { - if !negated { - return code - } - if code == 0 { - return 1 - } - return 0 -} diff --git a/cmd/sh/command.go b/cmd/sh/command.go deleted file mode 100644 index abfd175..0000000 --- a/cmd/sh/command.go +++ /dev/null @@ -1,251 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/johnstarich/go-wasm/internal/console" - "github.com/pkg/errors" - "mvdan.cc/sh/v3/syntax" -) - -const ( - homeTilde = "~" -) - -func runLine(term console.Console, line string) error { - parser := syntax.NewParser() - var cmdErr error - err := parser.Stmts(strings.NewReader(line), func(stmt *syntax.Stmt) bool { - cmdErr = runCommand(term, line, stmt, false) - return cmdErr == nil - }) - if err != nil { - return err - } - if cmdErr != nil { - return cmdErr - } - if parser.Incomplete() { - return errors.New("Incomplete command. Multi-line commands not supported.") - } - return nil -} - -func evalWord(parts []syntax.WordPart) (string, error) { - s := "" - for ix, part := range parts { - switch part := part.(type) { - case *syntax.Lit: - s += part.Value - if ix == 0 && (s == homeTilde || strings.HasPrefix(s, homeTilde+string(filepath.Separator))) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", err - } - s = homeDir + s[len(homeTilde):] - } - case *syntax.SglQuoted: - if part.Dollar { - return "", errors.Errorf("Dollar single-quotes not supported: %v", part) - } - s += part.Value - case *syntax.DblQuoted: - if part.Dollar { - return "", errors.Errorf("Dollar single-quotes not supported: %v", part) - } - dblQuoted, err := evalWord(part.Parts) - if err != nil { - return "", err - } - s += dblQuoted - case *syntax.ParamExp: - name := part.Param.Value - if part.Excl || part.Length || part.Width || part.Index != nil || part.Slice != nil || part.Repl != nil || part.Names != 0 || part.Exp != nil { - return "", errors.Errorf("Variable expansion type not supported: %s %v", name, part) - } - s += os.Getenv(name) - case *syntax.CmdSubst, *syntax.ArithmExp, *syntax.ProcSubst, *syntax.ExtGlob: - return "", errors.Errorf("Unrecognized word part type: %T %v", part, part) - default: - return "", errors.Errorf("Unrecognized word part type: %T %v", part, part) - } - } - return s, nil -} - -func runCommand(term console.Console, line string, stmt *syntax.Stmt, isPipe bool) error { - switch node := stmt.Cmd.(type) { - case *syntax.CallExpr: - return runCallExpr(term, stmt, node, isPipe) - case *syntax.BinaryCmd: - switch node.Op { - case syntax.AndStmt: // && - err := runCommand(term, line, node.X, false) - if err != nil { - return err - } - return runCommand(term, line, node.Y, false) - case syntax.OrStmt: // || - err := runCommand(term, line, node.X, false) - if err == nil { - return nil - } - return runCommand(term, line, node.Y, false) - case syntax.Pipe: // | - r, w, err := os.Pipe() - if err != nil { - return err - } - leftTerm := &redirectConsole{ - stdin: getConsoleStdin(term), - stdout: w, - stderr: term.Stderr(), - } - rightTerm := &redirectConsole{ - stdin: r, - stdout: term.Stdout(), - stderr: term.Stderr(), - } - errChan := make(chan error, 1) - go func() { - errChan <- runCommand(rightTerm, line, node.Y, true) - }() - err = runCommand(leftTerm, line, node.X, false) - if err != nil { - return err - } - w.Close() - return <-errChan - case syntax.PipeAll: // |& - r, w, err := os.Pipe() - if err != nil { - return err - } - leftTerm := &redirectConsole{ - stdin: getConsoleStdin(term), - stdout: w, - stderr: w, - } - rightTerm := &redirectConsole{ - stdin: r, - stdout: term.Stdout(), - stderr: term.Stderr(), - } - errChan := make(chan error, 1) - go func() { - errChan <- runCommand(rightTerm, line, node.Y, true) - }() - err = runCommand(leftTerm, line, node.X, false) - if err != nil { - return err - } - return <-errChan - default: - return errors.Errorf("Unknown binary operator: %v", node.Op) - } - - case *syntax.TimeClause: - start := time.Now() - err := runCommand(term, line, node.Stmt, false) - duration := time.Since(start) - fmt.Fprintf(term.Stdout(), "\n%s\t %v total\n", formatStmt(line, node.Stmt), duration) - return err - - case *syntax.IfClause, *syntax.WhileClause, *syntax.ForClause, *syntax.CaseClause, *syntax.Block, *syntax.Subshell, *syntax.FuncDecl, *syntax.ArithmCmd, *syntax.TestClause, *syntax.DeclClause, *syntax.LetClause, *syntax.CoprocClause: - return errors.Errorf("Unimplemented statement type: %T %v", stmt.Cmd, stmt.Cmd) - default: - return errors.Errorf("Unknown statement type: %T %v", stmt.Cmd, stmt.Cmd) - } -} - -func formatStmt(source string, s *syntax.Stmt) string { - return source[s.Pos().Offset():s.End().Offset()] -} - -type cmdOptions struct { - Background bool - Pipe bool -} - -func runCmd(cmd *exec.Cmd, options cmdOptions) error { - // ensure files are all attached by default. these are assumed to be set up already - if cmd.Stdin == nil || cmd.Stdout == nil || cmd.Stderr == nil { - panic("Standard files not set up") - } - - args := []string{cmd.Path} - if len(cmd.Args) > 0 { - args = cmd.Args - } - commandName, args := args[0], args[1:] - - builtin, isBuiltin := builtins[commandName] - if options.Pipe || !isBuiltin { - if options.Background { - return cmd.Start() - } else { - return cmd.Run() - } - } - - var oldKV, unsetKV []string - // override env for builtin - for _, pair := range cmd.Env { - key, value := splitKeyValue(pair) - if oldValue, isSet := os.LookupEnv(key); isSet { - oldKV = append(oldKV, key+"="+oldValue) - } else { - unsetKV = append(unsetKV, key) - } - os.Setenv(key, value) - } - err := builtin(&redirectConsole{ - stdin: cmd.Stdin, - stdout: cmd.Stdout, - stderr: cmd.Stderr, - }, args...) - // restore env - for _, pair := range oldKV { - key, value := splitKeyValue(pair) - os.Setenv(key, value) - } - for _, key := range unsetKV { - os.Unsetenv(key) - } - return errors.Wrap(err, commandName) -} - -type redirectConsole struct { - stdin io.Reader - stdout, stderr io.Writer -} - -func (c *redirectConsole) Stdin() io.Reader { - return c.stdin -} - -func (c *redirectConsole) Stdout() io.Writer { - return c.stdout -} - -func (c *redirectConsole) Stderr() io.Writer { - return c.stderr -} - -func (c *redirectConsole) Note() io.Writer { - return ioutil.Discard -} - -func getConsoleStdin(term console.Console) io.Reader { - if stdiner, ok := term.(interface{ Stdin() io.Reader }); ok { - return stdiner.Stdin() - } - return os.Stdin -} diff --git a/cmd/sh/completions.go b/cmd/sh/completions.go deleted file mode 100644 index ecee8ea..0000000 --- a/cmd/sh/completions.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - - "github.com/johnstarich/go-wasm/log" - "mvdan.cc/sh/v3/syntax" -) - -type Completion struct { - Completion string - Start, End int -} - -func getCompletions(line string, cursor int) []Completion { - completions, err := getCompletionsErr(line, cursor) - if err != nil { - log.Error("Failed completions: ", err) - return nil - } - return completions -} - -func getCompletionsErr(line string, cursor int) ([]Completion, error) { - parser := syntax.NewParser() - var stmts []*syntax.Stmt - err := parser.Stmts(strings.NewReader(line), func(stmt *syntax.Stmt) bool { - if int(stmt.Pos().Offset()) <= cursor && int(stmt.End().Offset()) >= cursor { - stmts = append(stmts, stmt) - } - return true - }) - if err != nil || len(stmts) == 0 { - return nil, err - } - cursorStmt := stmts[0] - cursorStmtStr := formatStmt(line, cursorStmt) - cursorStmtOffset := int(cursorStmt.Pos().Offset()) - cursor -= cursorStmtOffset - - var commandWord, cursorWord *syntax.Word - err = parser.Words(strings.NewReader(cursorStmtStr), func(word *syntax.Word) bool { - if commandWord == nil { - commandWord = word - } - if int(word.Pos().Offset()) <= cursor && int(word.End().Offset()) >= cursor { - cursorWord = word - } - return true - }) - if err != nil || cursorWord == nil { - return nil, err - } - - commandWordStr, err := evalWord(commandWord.Parts) - if err != nil { - return nil, err - } - cursorWordStr, err := evalWord(cursorWord.Parts) - if err != nil { - return nil, err - } - - return getStatementCompletions( - commandWordStr, - cursorWordStr, - cursorStmtOffset+int(cursorWord.Pos().Offset()), - cursorStmtOffset+int(cursorWord.End().Offset())) -} - -func getStatementCompletions(commandName string, word string, start, end int) ([]Completion, error) { - switch { - case strings.Contains(word, "/"): - dir := word - filter := false - info, err := os.Stat(dir) - if err != nil || !info.IsDir() { - dir = filepath.Dir(dir) - filter = true - } - dirEntries, err := os.ReadDir(dir) - if err != nil { - return nil, nil - } - var completions []Completion - for _, d := range dirEntries { - base := filepath.Base(word) - name := d.Name() - if !filter || strings.HasPrefix(name, base) { - file := fileJoin(dir, name) - if d.IsDir() { - file += string(filepath.Separator) - } - completions = append(completions, Completion{ - Completion: file, - Start: start, - End: end, - }) - } - } - return completions, nil - default: - return nil, nil - } -} - -func fileJoin(a, b string) string { - if a == "." { - return "." + string(filepath.Separator) + b - } - return filepath.Join(a, b) -} diff --git a/cmd/sh/cr_stream.go b/cmd/sh/cr_stream.go deleted file mode 100644 index 95ed9b2..0000000 --- a/cmd/sh/cr_stream.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "bytes" - "io" - "os" -) - -type carriageReturnWriter struct { - io.Writer -} - -func newCarriageReturnWriter(dest io.Writer) (*os.File, error) { - r, w, err := os.Pipe() - if err != nil { - return nil, err - } - dest = &carriageReturnWriter{dest} - - go func() { - _, _ = io.CopyBuffer(dest, r, make([]byte, 1)) - }() - return w, nil -} - -func (c *carriageReturnWriter) Write(p []byte) (n int, err error) { - newP := bytes.ReplaceAll(p, []byte("\n"), []byte("\n\r")) - n, err = c.Writer.Write(newP) - n -= len(newP) - len(p) - return -} diff --git a/cmd/sh/err_exit.go b/cmd/sh/err_exit.go deleted file mode 100644 index d60088b..0000000 --- a/cmd/sh/err_exit.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import "fmt" - -type ExitErr struct { - Code int -} - -func (e *ExitErr) Error() string { - return fmt.Sprintf("exit code %d", e.Code) -} diff --git a/cmd/sh/history.go b/cmd/sh/history.go deleted file mode 100644 index fde5891..0000000 --- a/cmd/sh/history.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "bufio" - "os" - "path/filepath" - "strings" -) - -var historyFile string - -func init() { - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - historyFile = filepath.Join(home, ".history") -} - -type history struct { - lastIndex int - lines []string -} - -func newHistory() (*history, error) { - lines, err := loadHistoryFile() - return &history{ - lines: lines, - }, err -} - -func loadHistoryFile() ([]string, error) { - historyFile, err := os.Open(historyFile) - if err != nil { - if os.IsNotExist(err) { - err = nil - } - return nil, err - } - defer historyFile.Close() - scanner := bufio.NewScanner(historyFile) - if err != nil { - return nil, err - } - var history []string - for i := 0; i < 100 && scanner.Scan(); i++ { - history = append(history, scanner.Text()) - } - return history, nil -} - -func (h *history) Push(command string) error { - command = strings.TrimSpace(command) - h.lastIndex = 0 - if command == "" || command == h.mostRecentCommand() { - return nil - } - - h.lines = append(h.lines, command) - f, err := os.OpenFile(historyFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) - if err != nil { - return err - } - defer f.Close() - _, err = f.WriteString(command + "\n") - return err -} - -func (h *history) mostRecentCommand() string { - if len(h.lines) > 0 { - return h.lines[len(h.lines)-1] - } - return "" -} - -func (h *history) Previous() (command string, ok bool) { - if h.lastIndex < len(h.lines) { - h.lastIndex++ - return h.lines[len(h.lines)-h.lastIndex], true - } - return "", false -} - -func (h *history) Next() (command string, ok bool) { - if h.lastIndex > 1 { - h.lastIndex-- - return h.lines[len(h.lines)-h.lastIndex], true - } - ok = h.lastIndex == 1 - h.lastIndex = 0 - return "", ok -} diff --git a/cmd/sh/js_builtins.go b/cmd/sh/js_builtins.go deleted file mode 100644 index 86137af..0000000 --- a/cmd/sh/js_builtins.go +++ /dev/null @@ -1,73 +0,0 @@ -// +build js,wasm - -package main - -import ( - "fmt" - "io/ioutil" - "strings" - "syscall/js" - - "github.com/fatih/color" - "github.com/johnstarich/go-wasm/internal/console" - "github.com/johnstarich/go-wasm/internal/interop" - "github.com/johnstarich/go-wasm/internal/promise" - "github.com/pkg/errors" -) - -var ( - jsFunction = js.Global().Get("Function") - goWasm = js.Global().Get("goWasm") -) - -func init() { - builtins["jseval"] = jseval - builtins["wpk"] = wpk - builtins["jsdownload"] = jsdownload - color.NoColor = false // override, since wasm isn't considered a "tty" -} - -func jsEval(funcStr string, args ...interface{}) js.Value { - f := jsFunction.Invoke(`"use strict";` + funcStr) - return f.Invoke(args...) -} - -func jseval(term console.Console, args ...string) error { - if len(args) < 1 { - return errors.New("Must provide a string to run as a function") - } - result := jsEval(args[0], strings.Join(args[1:], " ")) - fmt.Fprintln(term.Stdout(), result) - return nil -} - -func wpk(term console.Console, args ...string) error { - if len(args) < 2 { - return errors.New(strings.TrimSpace(` -Usage: wpk add - -Installs a remote package by the name of 'pkg'. -`)) - } - switch args[0] { - case "add": - prom := promise.From(goWasm.Call("install", args[1])) - _, err := prom.Await() - return err - default: - return errors.Errorf("Invalid command: %q", args[0]) - } -} - -func jsdownload(term console.Console, args ...string) error { - if len(args) < 1 { - return errors.New("Must provide a file to download") - } - filePath := args[0] - fileContents, err := ioutil.ReadFile(filePath) - if err != nil { - return errors.Wrap(err, "Error reading file for download") - } - interop.StartDownload("", filePath, fileContents) - return nil -} diff --git a/cmd/sh/main.go b/cmd/sh/main.go index 0982d07..11f9656 100644 --- a/cmd/sh/main.go +++ b/cmd/sh/main.go @@ -1,41 +1,15 @@ package main import ( - "flag" - "io" + "io/ioutil" + "log" "os" - "strings" + + "github.com/hack-pad/hush" ) func main() { - os.Exit(run()) -} - -func run() int { - cancel, err := ttySetup() - if err != nil { - panic(err) - } - defer cancel() - - command := flag.String("c", "", "Read and execute commands from the given string value.") - flag.Parse() - - var reader io.RuneReader - if *command != "" { - reader = newRuneReader(strings.NewReader(*command)) - } else { - reader = newRuneReader(os.Stdin) - } - os.Stdout, err = newCarriageReturnWriter(os.Stdout) - if err != nil { - panic(err) - } - os.Stderr, err = newCarriageReturnWriter(os.Stderr) - if err != nil { - panic(err) - } - term := newTerminal() - - return term.ReadEvalPrintLoop(reader) + log.SetOutput(ioutil.Discard) + exitCode := hush.Run() + os.Exit(exitCode) } diff --git a/cmd/sh/prompt.go b/cmd/sh/prompt.go deleted file mode 100644 index 2022832..0000000 --- a/cmd/sh/prompt.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - "text/template" - - "github.com/fatih/color" -) - -const ( - promptTemplateStr = `{{.RCArrow}} {{.CurDirName}} $ ` -) - -var ( - promptTemplate = template.Must(template.New("").Parse(promptTemplateStr)) -) - -func prompt(term *terminal) string { - s, err := promptErr(term) - if err != nil { - fmt.Fprintln(os.Stderr, "Failed to render prompt: ", err) - } - return s -} - -func promptErr(term *terminal) (string, error) { - var buf bytes.Buffer - data, err := newPromptData(term) - if err != nil { - return "", err - } - err = promptTemplate.Execute(&buf, data) - return buf.String(), err -} - -type promptData struct { - RCArrow string - CurDirName string -} - -func newPromptData(term *terminal) (data *promptData, err error) { - const rcArrow = "➜" - data = &promptData{ - RCArrow: color.GreenString(rcArrow), - } - - wd, err := os.Getwd() - if err != nil { - return - } - home, err := os.UserHomeDir() - if err != nil { - return - } - data.CurDirName = filepath.Base(wd) - if wd == home { - data.CurDirName = "~" - } - - if term.lastExitCode != 0 { - data.RCArrow = color.RedString(rcArrow) - } - - return -} diff --git a/cmd/sh/rune_reader.go b/cmd/sh/rune_reader.go deleted file mode 100644 index ea1661f..0000000 --- a/cmd/sh/rune_reader.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "io" - "unicode/utf8" -) - -type runeReader struct { - io.Reader -} - -func newRuneReader(r io.Reader) io.RuneReader { - return &runeReader{r} -} - -func (b *runeReader) ReadRune() (r rune, n int, err error) { - oneByte := make([]byte, 1) - var buf []byte - for !utf8.FullRune(buf) { - n, err = b.Read(oneByte) - if err != nil { - r = utf8.RuneError - return - } - buf = append(buf, oneByte[0]) - } - r, n = utf8.DecodeRune(buf) - return -} diff --git a/cmd/sh/table.go b/cmd/sh/table.go deleted file mode 100644 index d83a4a6..0000000 --- a/cmd/sh/table.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type table struct { - rows [][]string - align []columnAlign -} - -type columnAlign int - -const ( - leftAlign columnAlign = iota - rightAlign -) - -func (t *table) Align(alignment ...columnAlign) { - t.align = alignment -} - -func (t *table) Add(columns ...interface{}) { - stringColumns := make([]string, len(columns)) - for i := range columns { - stringColumns[i] = strings.TrimSpace(fmt.Sprint(columns[i])) - } - t.rows = append(t.rows, stringColumns) -} - -func (t table) String() string { - var columnWidths []int - for _, row := range t.rows { - if len(columnWidths) < len(row) { - columnWidths = append(columnWidths, make([]int, len(row)-len(columnWidths))...) - } - for ix, col := range row { - if len(col) > columnWidths[ix] { - columnWidths[ix] = len(col) - } - } - } - - const colSeparator = ' ' - var s strings.Builder - for _, row := range t.rows { - for ix, col := range row { - align := leftAlign - if len(t.align) > ix { - align = t.align[ix] - } - switch align { - case rightAlign: - s.WriteString(padString(columnWidths[ix] - len(col))) - s.WriteString(col) - default: - s.WriteString(col) - s.WriteString(padString(columnWidths[ix] - len(col))) - } - s.WriteRune(colSeparator) - } - s.WriteRune('\n') - } - return s.String() -} - -func padString(length int) string { - return fmt.Sprintf(fmt.Sprintf("%%%ds", length), "") -} diff --git a/cmd/sh/terminal.go b/cmd/sh/terminal.go deleted file mode 100644 index e559b7e..0000000 --- a/cmd/sh/terminal.go +++ /dev/null @@ -1,432 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "runtime/debug" - "strings" - "unicode" - - "github.com/fatih/color" - "github.com/johnstarich/go-wasm/log" - "github.com/pkg/errors" -) - -const ( - controlBackspace = '\x7F' - controlClear = '\f' - controlCloseStdin = '\x04' - controlSigTStop = '\x03' - controlCursorBackward = 'D' - controlCursorDown = 'B' - controlCursorForward = 'C' - controlCursorUp = 'A' - controlDeleteWord = '\x17' - controlEnd = '\x05' - controlEnter = '\r' - controlHome = '\x01' - controlNextWord = '\x66' - controlPreviousWord = '\x62' - controlScroll = '\x4f' - escapeCSI = '\x1B' - escapeLBracket = '[' -) - -type terminal struct { - // reader state - line []rune - cursor int - // command state - lastExitCode int - history *history -} - -func newTerminal() *terminal { - term := &terminal{} - history, err := newHistory() - if err != nil { - term.ErrPrint(color.RedString(err.Error()) + "\n") - } - term.history = history - return term -} - -func (t *terminal) Stdout() io.Writer { - return os.Stdout -} - -func (t *terminal) Stderr() io.Writer { - return os.Stderr -} - -func (t *terminal) Note() io.Writer { - return ioutil.Discard -} - -func (t *terminal) Print(args ...interface{}) { - fmt.Fprint(t.Stdout(), args...) -} - -func (t *terminal) Printf(format string, args ...interface{}) { - fmt.Fprintf(t.Stdout(), format, args...) -} - -func (t *terminal) ErrPrint(args ...interface{}) { - fmt.Fprint(t.Stderr(), args...) -} - -func (t *terminal) ReadEvalPrintLoop(reader io.RuneReader) int { - fmt.Fprint(t.Stdout(), prompt(t)) - for { - err := t.ReadEvalPrint(reader) - if exitErr, ok := err.(*ExitErr); ok { - return exitErr.Code - } - if err == io.EOF || unwrapErr(err) == os.ErrClosed { - return 0 - } - if err != nil { - log.Error("Critical error during REPL: ", err) - return 1 - } - } -} - -func unwrapErr(err error) error { - for { - unwrapper, ok := err.(interface{ Unwrap() error }) - if !ok { - return err - } - newErr := unwrapper.Unwrap() - if newErr != nil { - err = newErr - } - } -} - -func (t *terminal) ReadEvalPrint(reader io.RuneReader) error { - defer func() { - if r := recover(); r != nil { - msg := fmt.Sprintf("\n\npanic: %s\n%s\n", r, string(debug.Stack())) - t.ErrPrint(color.RedString(msg)) - - // attempt to return to a recovered state - t.line = nil - t.cursor = 0 - t.lastExitCode = 1 - t.Print(prompt(t)) - } - }() - - r, _, err := reader.ReadRune() - if err == io.EOF { - return io.EOF - } - if err != nil { - return err - } - - switch r { - case escapeCSI: - err := t.ReadEvalEscape(r, reader) - if err != nil { - log.Error("Error reading from stdin: ", err) - } - case controlBackspace: - if t.cursor > 0 { - t.cursor-- - runes, suffix := splitRunes(t.line, t.cursor) - suffix = suffix[1:] // trim off char after decremented cursor - runes = append(runes, suffix...) - t.line = runes - t.CursorLeftN(1) - t.ClearRightN(len(t.line) - t.cursor + 1) - t.Print(string(t.line[t.cursor:])) - t.CursorLeftN(len(t.line) - t.cursor) - } - case controlClear: - t.Clear() - t.Print(prompt(t)) - case controlEnter: - t.eraseBelowPrompt() - t.Print("\r\n") - command := string(t.line) - t.line = nil - t.cursor = 0 - err = runLine(t, command) - t.lastExitCode = 0 - if err != nil { - t.ErrPrint(color.RedString(err.Error()) + "\n") - t.lastExitCode = 1 - if exitErr, ok := err.(*exec.ExitError); ok { - t.lastExitCode = exitErr.ExitCode() - } - } - err := t.history.Push(command) - if err != nil { - t.ErrPrint(color.RedString(err.Error()) + "\n") - } - t.Print(prompt(t)) - case controlDeleteWord: - t.deleteWord() - case controlEnd: - t.moveCursorToEnd() - case controlHome: - t.moveCursorToStart() - case controlCloseStdin: - return &ExitErr{Code: 0} - case controlSigTStop: - t.line = nil - t.cursor = 0 - err := t.history.Push("") // resets history index, no error should be possible - if err != nil { - panic(err) - } - t.lastExitCode = 1 - t.Print("^C\n\r") - t.Print(prompt(t)) - case '\t': - completions := getCompletions(string(t.line), t.cursor) - t.eraseBelowPrompt() - if len(completions) == 1 { - completion := completions[0] - t.ClearRightN(len(t.line) - completion.End) - t.CursorLeftN(t.cursor - completion.Start) - t.ClearRightN(t.cursor - completion.Start) - line := string(t.line) - prefix, suffix := line[:completion.Start], line[completion.End:] - line = prefix + completion.Completion + suffix - t.Print(completion.Completion) - t.Print(suffix) - t.CursorLeftN(len(suffix)) - t.line = []rune(line) - t.cursor = len(t.line) - len(suffix) - } else if len(completions) > 1 { - t.SaveCursor() - t.Print("\n") - for _, completion := range completions { - t.Print(completion.Completion, "\t") - } - t.RestoreCursor() - } - default: - prefix, suffix := splitRunes(t.line, t.cursor) - t.cursor++ - t.line = append(append(prefix, r), suffix...) - t.Print(string(t.line[t.cursor-1:])) - t.CursorLeftN(len(t.line) - t.cursor) - } - if t.cursor > len(t.line) { - panic(fmt.Sprint("Cursor too large: cursor =", t.cursor, "length =", len(t.line))) - } - log.Debugf("Term = %q %d; Cursor = %q %d", string(t.line), len(t.line), string(t.line[t.cursor:]), t.cursor) - return nil -} - -func splitRunes(runes []rune, i int) (a, b []rune) { - a = append([]rune{}, runes[:i]...) - b = append([]rune{}, runes[i:]...) - return -} - -func (t *terminal) ReadEvalEscape(firstRune rune, r io.RuneReader) error { - controlRune, _, err := r.ReadRune() - if err != nil { - return err - } - switch controlRune { - case controlBackspace: - t.deleteWord() - return nil - case controlPreviousWord: - beforeCursor := string(t.line[:t.cursor]) - beforeCursor = strings.TrimRightFunc(beforeCursor, unicode.IsSpace) - prevWord := strings.LastIndexFunc(beforeCursor, unicode.IsSpace) + 1 - t.CursorLeftN(t.cursor - prevWord) - t.cursor = prevWord - return nil - case controlNextWord: - afterCursor := string(t.line[t.cursor:]) - afterCursor = strings.TrimLeftFunc(afterCursor, func(r rune) bool { - return !unicode.IsSpace(r) - }) - afterCursor = strings.TrimLeftFunc(afterCursor, unicode.IsSpace) - nextWord := len(t.line) - len(afterCursor) - t.CursorRightN(nextWord - t.cursor) - t.cursor = nextWord - return nil - case controlScroll: // ignore: this is a pre-cursor to a scroll event, but unsure if it should have a special action - case escapeLBracket: - default: - t.Print(string(controlRune)) - return errors.Errorf(`Invalid escape sequence: \x%x \x%x`, escapeCSI, controlRune) - } - - var controlParams []rune - for { - controlRune, _, err = r.ReadRune() - if err != nil { - return err - } - if !unicode.IsDigit(controlRune) && controlRune != ';' { - break - } - controlParams = append(controlParams, controlRune) - } - - escape := append(append([]rune{escapeCSI, escapeLBracket}, controlParams...), controlRune) - log.Debugf("Got escape sequence: %q", escape) - switch controlRune { - case controlCursorUp: - previousCommand, ok := t.history.Previous() - if ok { - t.CursorLeftN(t.cursor) - t.ClearRightN(len(t.line)) - t.line = []rune(previousCommand) - t.cursor = len(t.line) - t.Print(previousCommand) - } - return nil - case controlCursorDown: - nextCommand, _ := t.history.Next() - t.CursorLeftN(t.cursor) - t.ClearRightN(len(t.line)) - t.line = []rune(nextCommand) - t.cursor = len(t.line) - t.Print(nextCommand) - return nil - case controlCursorForward: - if t.cursor >= len(t.line) { - return nil - } - t.cursor++ - case controlCursorBackward: - if t.cursor <= 0 { - return nil - } - t.cursor-- - case 'E': // cursor next line - return nil - case 'F': // end key (also cursor backward?) - t.moveCursorToEnd() - return nil - case 'H': // home key - t.moveCursorToStart() - return nil - case '~': // forward delete - if t.cursor != len(t.line) { - runes, suffix := splitRunes(t.line, t.cursor) - suffix = suffix[1:] - runes = append(runes, suffix...) - t.line = runes - t.ClearRightN(len(t.line) - t.cursor + 1) - t.Print(string(t.line[t.cursor:])) - t.CursorLeftN(len(t.line) - t.cursor) - } - return nil - default: - // ignore by default - return nil - } - str := string(escape) - t.Print(str) - return nil -} - -func (t *terminal) ClearRightN(n int) { - if n <= 0 { - return - } - t.Printf("%c%c%dX", escapeCSI, escapeLBracket, n) -} - -func (t *terminal) CursorUpN(n int) { - if n <= 0 { - return - } - t.Printf("%c%c%d%c", escapeCSI, escapeLBracket, n, controlCursorUp) -} - -func (t *terminal) CursorDownN(n int) { - if n <= 0 { - return - } - t.Printf("%c%c%d%c", escapeCSI, escapeLBracket, n, controlCursorDown) -} - -func (t *terminal) CursorLeftN(n int) { - if n <= 0 { - return - } - t.Printf("%c%c%d%c", escapeCSI, escapeLBracket, n, controlCursorBackward) -} - -func (t *terminal) CursorRightN(n int) { - if n <= 0 { - return - } - t.Printf("%c%c%d%c", escapeCSI, escapeLBracket, n, controlCursorForward) -} - -func (t *terminal) SaveCursor() { - t.Printf("%c%c%s", escapeCSI, escapeLBracket, "s") -} - -func (t *terminal) RestoreCursor() { - t.Printf("%c%c%s", escapeCSI, escapeLBracket, "u") -} - -func (t *terminal) Clear() { - // TODO this wipes out some scrollback, need to figure out how to preserve it - t.Print(string(escapeCSI) + "[H") // set cursor to top left - t.Print(string(escapeCSI) + "[J") // clear viewport -} - -func (t *terminal) deleteWord() { - originalLen := len(t.line) - var trimmed []rune - t.line, trimmed = deleteWord(t.line, t.cursor) - trimmedLen := len(trimmed) - t.cursor -= trimmedLen - t.CursorLeftN(trimmedLen) - t.ClearRightN(originalLen - t.cursor) - remaining := t.line[t.cursor:] - t.Print(string(remaining)) - t.CursorLeftN(len(remaining)) -} - -func deleteWord(s []rune, cursor int) (newLine, trimmed []rune) { - if cursor == 0 { - return s, nil - } - - str := string(s[:cursor]) - str = strings.TrimRightFunc(str, unicode.IsSpace) - previousWord := strings.LastIndexFunc(str, unicode.IsSpace) + 1 - // not found is: -1 + 1 == 0 - // finding a word is: lastSpaceIndex + 1 - - newS := string(s[:previousWord]) + string(s[cursor:]) - return []rune(newS), s[previousWord:cursor] -} - -func (t *terminal) moveCursorToStart() { - t.CursorLeftN(t.cursor) - t.cursor = 0 -} - -func (t *terminal) moveCursorToEnd() { - t.CursorRightN(len(t.line) - t.cursor) - t.cursor = len(t.line) -} - -func (t *terminal) eraseBelowPrompt() { - t.SaveCursor() - t.Print("\n\r") - t.Printf("%c%c%d%c", escapeCSI, escapeLBracket, 0, 'J') // erase from cursor to end of viewport - t.RestoreCursor() -} diff --git a/cmd/sh/tty_js.go b/cmd/sh/tty_js.go deleted file mode 100644 index 1c9dd26..0000000 --- a/cmd/sh/tty_js.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build js - -package main - -import "context" - -func ttySetup() (context.CancelFunc, error) { - return func() {}, nil -} diff --git a/cmd/sh/tty_other.go b/cmd/sh/tty_other.go deleted file mode 100644 index 6f6936d..0000000 --- a/cmd/sh/tty_other.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build !js - -package main - -import ( - "context" - "fmt" - "os" - - gotty "github.com/mattn/go-tty" -) - -func ttySetup() (context.CancelFunc, error) { - tty, err := gotty.Open() - if err != nil { - return nil, err - } - cancel, err := tty.Raw() - if err != nil { - return nil, err - } - os.Stdin = tty.Input() - return func() { - err := cancel() - if err != nil { - fmt.Fprintln(os.Stderr, "Failed to restore tty:", err) - } - tty.Close() - }, nil -} diff --git a/go.mod b/go.mod index adbab0f..cc6ca56 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,12 @@ go 1.16 require ( github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1 - github.com/fatih/color v1.9.0 github.com/hack-pad/go-indexeddb v0.1.0 github.com/hack-pad/hackpadfs v0.1.0 + github.com/hack-pad/hush v0.0.0-20210730065049-bd589dbef3a3 github.com/johnstarich/go/datasize v0.0.1 github.com/machinebox/progress v0.2.0 - github.com/mattn/go-tty v0.0.3 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.5.1 go.uber.org/atomic v1.6.0 - mvdan.cc/sh/v3 v3.1.2 ) diff --git a/go.sum b/go.sum index fa09194..d48145c 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,24 @@ github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1 h1:9h8f71kuF1pqovnn9h7LTHLEjxzyQaj0j1rQq5nsMM4= github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/google/renameio v1.0.1-0.20210406141108-81588dbe0453/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/hack-pad/go-indexeddb v0.1.0 h1:UzRAl6WiKxLJePkgi2uaQa9MMPWcjO29zI3pt9D+rNs= github.com/hack-pad/go-indexeddb v0.1.0/go.mod h1:NH8CaojufPNcKYDhy5JkjfyBXE/72oJPeiywlabN/lM= github.com/hack-pad/hackpadfs v0.1.0 h1:3bItjrgASvPwOU9WZEzcA8pHJRy7FK8Dk6NaQN89SM4= github.com/hack-pad/hackpadfs v0.1.0/go.mod h1:8bsINHOQhQUioUUiCzCyZZNLfEXjs0RwBIf3lTG+CEg= +github.com/hack-pad/hush v0.0.0-20210730065049-bd589dbef3a3 h1:0WBvEONkD8zXBRe7+5+mp34L2Upmok0yPKvOqOzpksw= +github.com/hack-pad/hush v0.0.0-20210730065049-bd589dbef3a3/go.mod h1:NqjEIfyA2YtlnEPlI/1K3tNuyXGByWFadPxPlGrDPms= github.com/johnstarich/go/datasize v0.0.1 h1:Hjswen8gwmO7trXtQ8Xl8NUOQAKKm1wvus3/xK5eHGY= github.com/johnstarich/go/datasize v0.0.1/go.mod h1:4eHLMGz7Q5uCmZeS9rZdahvAih1QmBg1EW3bBXTJpi4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -24,25 +27,24 @@ github.com/machinebox/progress v0.2.0 h1:7z8+w32Gy1v8S6VvDoOPPBah3nLqdKjr3GUly18 github.com/machinebox/progress v0.2.0/go.mod h1:hl4FywxSjfmkmCrersGhmJH7KwuKl+Ueq9BXkOny+iE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= -github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/rogpeppe/go-internal v1.7.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= @@ -54,20 +56,22 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867 h1:JoRuNIf+rpHl+VhScRQQvzbHed86tKkqwPMV34T8myw= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324 h1:pAwJxDByZctfPwzlNGrDN2BQLsdPb9NkhoTJtUkAO28= +golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -75,6 +79,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8= -mvdan.cc/sh/v3 v3.1.2 h1:PG5BYlwtrkZTbJXUy25r0/q9shB5ObttCaknkOIB1XQ= -mvdan.cc/sh/v3 v3.1.2/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno= +mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= +mvdan.cc/sh/v3 v3.3.0 h1:ujzElMnry63f4I5sjPFxzo6xia+gwsHZM0yyauuyZ6k= +mvdan.cc/sh/v3 v3.3.0/go.mod h1:dh3avhLDhJJ/MJKzbak6FYn+DJKUWk7Fb6Dh5mGdv6Y=