| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- 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 {
- return core.ActionResult{
- Code: core.ActionResultCodeExit,
- ExitStatus: 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)
- }
|