package builtin import "fmt" import "os" import "os/exec" import "strings" import "github.com/codecrafters-io/shell-starter-go/app/core" type Builtin interface { Run(ctx *core.Context) core.ActionResult ParseArgs(ctx *core.Context, args []string) error } func wrapEs(es int) core.ActionResult { return core.ActionResult{ Code: core.ActionResultCodeBuiltinCommand, ExitStatus: core.ExitStatus(es), } } func Parse(ctx *core.Context, name string, args []string) (Builtin, error) { selected, err := selectBuiltin(name) if err != nil { return nil, err } if err := selected.ParseArgs(ctx, args); err != nil { return nil, err } return selected, nil } func selectBuiltin(name string) (Builtin, error) { switch name { case "cd": return &CdBuiltin{}, nil case "echo": return &EchoBuiltin{}, nil case "exit": return &ExitBuiltin{}, nil case "pwd": return &PwdBuiltin{}, nil case "type": return &TypeBuiltin{}, nil default: return nil, ParseError{Code: ParseErrorCodeUnknownBuiltin, value: name} } } type ParseError struct { Code ParseErrorCode value string } type ParseErrorCode int func (self ParseError) Error() string { switch self.Code { case ParseErrorCodeUsage: return fmt.Sprintf("usage: %s", self.value) case ParseErrorCodeUnknownBuiltin: // this should not be seen in normal REPL (we fallback to external command) return fmt.Sprintf("unknown built-in: %s", self.value) default: panic(self.Code) } } const ( ParseErrorCodeUsage ParseErrorCode = iota ParseErrorCodeUnknownBuiltin ) func makeUsage(pattern string) ParseError { return ParseError{ Code: ParseErrorCodeUsage, value: pattern, } } // // builtin: "cd" // type CdBuiltin struct { path string } func (self *CdBuiltin) ParseArgs(ctx *core.Context, args []string) error { usage := makeUsage("cd [PATH]") self.path = "" switch len(args) { case 0: return usage case 1: self.path = args[0] return nil default: return usage } } func (self *CdBuiltin) Run(ctx *core.Context) core.ActionResult { chdir := func(path string) core.ActionResult { err := os.Chdir(path) if err != nil { fmt.Fprintf(ctx.Stderr, "cd: %s: No such file or directory\n", path) return wrapEs(4) } return wrapEs(0) } if self.path == "~" || self.path == "" { home_path := os.Getenv("HOME") if len(home_path) == 0 { fmt.Fprintln(ctx.Stderr, "error: $HOME environment variable is empty or unset") return wrapEs(4) } return chdir(home_path) } return chdir(self.path) } // // builtin: "echo" // type EchoBuiltin struct { args []string } func (self *EchoBuiltin) ParseArgs(ctx *core.Context, args []string) error { self.args = args return nil } func (self *EchoBuiltin) Run(ctx *core.Context) core.ActionResult { fmt.Fprintln(ctx.Stdout, strings.Join(self.args, " ")) return wrapEs(0) } // // builtin: "exit" // type ExitBuiltin struct { } func (self *ExitBuiltin) ParseArgs(ctx *core.Context, args []string) error { if len(args) != 0 { return makeUsage("exit") } return nil } func (self *ExitBuiltin) Run(ctx *core.Context) core.ActionResult { os.Exit(0) return wrapEs(0) } // // builtin: "pwd" // type PwdBuiltin struct { } func (self *PwdBuiltin) ParseArgs(ctx *core.Context, args []string) error { if len(args) != 0 { return makeUsage("pwd") } return nil } func (self *PwdBuiltin) Run(ctx *core.Context) core.ActionResult { cwd_path, err := os.Getwd() if err != nil { panic("error getting current working directory") } fmt.Fprintln(ctx.Stdout, cwd_path) return wrapEs(0) } // // builtin: "type" // type TypeBuiltin struct { query string } func (self *TypeBuiltin) ParseArgs(ctx *core.Context, args []string) error { if len(args) != 1 { return makeUsage("type COMMAND") } self.query = args[0] return nil } func (self *TypeBuiltin) Run(ctx *core.Context) core.ActionResult { _, err := selectBuiltin(self.query) if err == nil { fmt.Fprintf(ctx.Stdout, "%s is a shell builtin\n", self.query) return wrapEs(0) } exec_path, err := exec.LookPath(self.query) if err == nil { fmt.Fprintf(ctx.Stdout, "%s is %s\n", self.query, exec_path) return wrapEs(0) } fmt.Fprintf(ctx.Stderr, "%s: not found\n", self.query) return wrapEs(0) }