123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package builtin
  2. import "fmt"
  3. import "os"
  4. import "os/exec"
  5. import "strings"
  6. import "github.com/codecrafters-io/shell-starter-go/app/core"
  7. type Builtin interface {
  8. Run(ctx *core.Context) core.ExitStatus
  9. ParseArgs(ctx *core.Context, args []string) error
  10. }
  11. func Parse(ctx *core.Context, name string, args []string) (Builtin, error) {
  12. selected, err := selectBuiltin(name)
  13. if err != nil {
  14. return nil, err
  15. }
  16. if err := selected.ParseArgs(ctx, args); err != nil {
  17. return nil, err
  18. }
  19. return selected, nil
  20. }
  21. func selectBuiltin(name string) (Builtin, error) {
  22. switch name {
  23. case "cd":
  24. return &CdBuiltin{}, nil
  25. case "echo":
  26. return &EchoBuiltin{}, nil
  27. case "exit":
  28. return &ExitBuiltin{}, nil
  29. case "pwd":
  30. return &PwdBuiltin{}, nil
  31. case "type":
  32. return &TypeBuiltin{}, nil
  33. default:
  34. return nil, ParseError{Code: ParseErrorCodeUnknownBuiltin, value: name}
  35. }
  36. }
  37. type ParseError struct {
  38. Code ParseErrorCode
  39. value string
  40. }
  41. type ParseErrorCode int
  42. func (self ParseError) Error() string {
  43. switch self.Code {
  44. case ParseErrorCodeUsage:
  45. return fmt.Sprintf("usage: %s", self.value)
  46. case ParseErrorCodeUnknownBuiltin:
  47. // this should not be seen in normal REPL (we fallback to external command)
  48. return fmt.Sprintf("unknown built-in: %s", self.value)
  49. default:
  50. panic(self.Code)
  51. }
  52. }
  53. const (
  54. ParseErrorCodeUsage ParseErrorCode = iota
  55. ParseErrorCodeUnknownBuiltin
  56. )
  57. func makeUsage(pattern string) ParseError {
  58. return ParseError{
  59. Code: ParseErrorCodeUsage,
  60. value: pattern,
  61. }
  62. }
  63. //
  64. // builtin: "cd"
  65. //
  66. type CdBuiltin struct {
  67. path string
  68. }
  69. func (self *CdBuiltin) ParseArgs(ctx *core.Context, args []string) error {
  70. usage := makeUsage("cd [PATH]")
  71. self.path = ""
  72. switch len(args) {
  73. case 0:
  74. return usage
  75. case 1:
  76. self.path = args[0]
  77. return nil
  78. default:
  79. return usage
  80. }
  81. }
  82. func (self *CdBuiltin) Run(ctx *core.Context) core.ExitStatus {
  83. chdir := func(path string) core.ExitStatus {
  84. err := os.Chdir(path)
  85. if err != nil {
  86. fmt.Fprintf(ctx.Stderr, "cd: %s: No such file or directory\n", path)
  87. return 4
  88. }
  89. return 0
  90. }
  91. if self.path == "~" || self.path == "" {
  92. home_path := os.Getenv("HOME")
  93. if len(home_path) == 0 {
  94. fmt.Fprintln(ctx.Stderr, "error: $HOME environment variable is empty or unset")
  95. return 4
  96. }
  97. return chdir(home_path)
  98. }
  99. return chdir(self.path)
  100. }
  101. //
  102. // builtin: "echo"
  103. //
  104. type EchoBuiltin struct {
  105. args []string
  106. }
  107. func (self *EchoBuiltin) ParseArgs(ctx *core.Context, args []string) error {
  108. self.args = args
  109. return nil
  110. }
  111. func (self *EchoBuiltin) Run(ctx *core.Context) core.ExitStatus {
  112. fmt.Fprintln(ctx.Stdout, strings.Join(self.args, " "))
  113. return 0
  114. }
  115. //
  116. // builtin: "exit"
  117. //
  118. type ExitBuiltin struct {
  119. }
  120. func (self *ExitBuiltin) ParseArgs(ctx *core.Context, args []string) error {
  121. if len(args) != 0 {
  122. return makeUsage("exit")
  123. }
  124. return nil
  125. }
  126. func (self *ExitBuiltin) Run(ctx *core.Context) core.ExitStatus {
  127. os.Exit(0)
  128. return 0
  129. }
  130. //
  131. // builtin: "pwd"
  132. //
  133. type PwdBuiltin struct {
  134. }
  135. func (self *PwdBuiltin) ParseArgs(ctx *core.Context, args []string) error {
  136. if len(args) != 0 {
  137. return makeUsage("pwd")
  138. }
  139. return nil
  140. }
  141. func (self *PwdBuiltin) Run(ctx *core.Context) core.ExitStatus {
  142. cwd_path, err := os.Getwd()
  143. if err != nil {
  144. panic("error getting current working directory")
  145. }
  146. fmt.Fprintln(ctx.Stdout, cwd_path)
  147. return 0
  148. }
  149. //
  150. // builtin: "type"
  151. //
  152. type TypeBuiltin struct {
  153. query string
  154. }
  155. func (self *TypeBuiltin) ParseArgs(ctx *core.Context, args []string) error {
  156. if len(args) != 1 {
  157. return makeUsage("type COMMAND")
  158. }
  159. self.query = args[0]
  160. return nil
  161. }
  162. func (self *TypeBuiltin) Run(ctx *core.Context) core.ExitStatus {
  163. _, err := selectBuiltin(self.query)
  164. if err == nil {
  165. fmt.Fprintf(ctx.Stdout, "%s is a shell builtin\n", self.query)
  166. return 0
  167. }
  168. exec_path, err := exec.LookPath(self.query)
  169. if err == nil {
  170. fmt.Fprintf(ctx.Stdout, "%s is %s\n", self.query, exec_path)
  171. return 0
  172. }
  173. fmt.Fprintf(ctx.Stderr, "%s: not found\n", self.query)
  174. return 0
  175. }