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