runnable.go 2.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package runnable
  2. import "os/exec"
  3. import "fmt"
  4. import "github.com/codecrafters-io/shell-starter-go/app/core"
  5. import "github.com/codecrafters-io/shell-starter-go/app/tokenize"
  6. import "github.com/codecrafters-io/shell-starter-go/app/builtin"
  7. type Runnable interface {
  8. Run(ctx *core.Context) core.ExitStatus
  9. }
  10. type ExternalCommand struct {
  11. Args []string
  12. ExecPath string
  13. Name string
  14. }
  15. func (self ExternalCommand) Run(ctx *core.Context) core.ExitStatus {
  16. cmd := exec.Command(self.ExecPath, self.Args...)
  17. cmd.Args[0] = self.Name
  18. cmd.Stdout = ctx.Stdout
  19. cmd.Stderr = ctx.Stderr
  20. cmd.Stdin = ctx.Stdin
  21. err := cmd.Run()
  22. if err != nil {
  23. if exiterr, ok := err.(*exec.ExitError); ok {
  24. return core.ExitStatus(exiterr.ExitCode())
  25. // fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode())
  26. } else {
  27. fmt.Printf("Handle():err=%#v\n", err)
  28. return core.ExitStatus(65535)
  29. }
  30. }
  31. return 0
  32. }
  33. type ParseError struct {
  34. code ParseErrorCode
  35. value string
  36. }
  37. type ParseErrorCode int
  38. const (
  39. ParseErrorCodeNothing ParseErrorCode = iota
  40. ParseErrorCodeNotFound
  41. )
  42. func (e ParseError) Error() string {
  43. switch e.code {
  44. case ParseErrorCodeNotFound:
  45. return fmt.Sprintf("%s: not found", e.value)
  46. case ParseErrorCodeNothing:
  47. return ""
  48. default:
  49. return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value)
  50. }
  51. }
  52. func Parse(ctx *core.Context, command_line string) (Runnable, error) {
  53. tokens := tokenize.Tokenize(command_line)
  54. if len(tokens) == 0 { // ie. empty or whitespace-only input
  55. return nil, ParseError{
  56. code: ParseErrorCodeNothing,
  57. }
  58. }
  59. bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
  60. if err == nil { // it was a builtin
  61. return bltn, nil
  62. }
  63. if e, ok := err.(builtin.ParseError); ok {
  64. switch e.Code {
  65. case builtin.ParseErrorCodeUsage:
  66. return nil, err
  67. case builtin.ParseErrorCodeUnknownBuiltin:
  68. exec_path, err := exec.LookPath(tokens[0])
  69. if err != nil { // command not found
  70. return nil, ParseError{
  71. code: ParseErrorCodeNotFound,
  72. value: tokens[0],
  73. }
  74. }
  75. return ExternalCommand{
  76. ExecPath: exec_path,
  77. Name: tokens[0],
  78. Args: tokens[1:],
  79. }, nil
  80. }
  81. }
  82. panic("unexpected builtin.Parse(); only builtin.ParseError is expected")
  83. }