package runnable import "os/exec" import "fmt" import "github.com/codecrafters-io/shell-starter-go/app/core" import "github.com/codecrafters-io/shell-starter-go/app/tokenize" import "github.com/codecrafters-io/shell-starter-go/app/builtin" type Runnable interface { Run(ctx *core.Context) core.ActionResult } type ExternalCommand struct { Args []string ExecPath string Name string } func (self ExternalCommand) Run(ctx *core.Context) core.ActionResult { wrapEs := func(es int) core.ActionResult { return core.ActionResult{ Code: core.ActionResultCodeExternalCommand, ExitStatus: core.ExitStatus(es), } } cmd := exec.Command(self.ExecPath, self.Args...) cmd.Args[0] = self.Name cmd.Stdout = ctx.Stdout cmd.Stderr = ctx.Stderr cmd.Stdin = ctx.Stdin err := cmd.Run() if err != nil { if exiterr, ok := err.(*exec.ExitError); ok { return wrapEs(exiterr.ExitCode()) // fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode()) } else { fmt.Printf("Handle():err=%#v\n", err) return wrapEs(65535) } } return wrapEs(0) } type ParseError struct { code ParseErrorCode value string } type ParseErrorCode int const ( ParseErrorCodeNotFound ParseErrorCode = iota ) func (e ParseError) Error() string { switch e.code { case ParseErrorCodeNotFound: return fmt.Sprintf("%s: not found", e.value) default: return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value) } } func Parse(ctx *core.Context, command_line string) (Runnable, error) { tokens, err := tokenize.Tokenize(command_line) if err != nil { return nil, err } //fmt.Printf("Parse():tokens=%#v\n", tokens) if len(tokens) == 0 { // ie. empty or whitespace-only input return Noop{}, nil } bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:]) if err == nil { // it was a builtin return bltn, nil } if e, ok := err.(builtin.ParseError); ok { switch e.Code { case builtin.ParseErrorCodeUsage: return nil, err case builtin.ParseErrorCodeUnknownBuiltin: exec_path, err := exec.LookPath(tokens[0]) if err != nil { // command not found return nil, ParseError{ code: ParseErrorCodeNotFound, value: tokens[0], } } return ExternalCommand{ ExecPath: exec_path, Name: tokens[0], Args: tokens[1:], }, nil } } panic("unexpected builtin.Parse(); only builtin.ParseError is expected") } type Noop struct{} func (self Noop) Run(ctx *core.Context) core.ActionResult { return core.ActionResult{ Code: core.ActionResultCodeNoop, ExitStatus: 0, } }