runnable.go 2.3KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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, err := tokenize.Tokenize(command_line)
  54. if err != nil {
  55. return nil, err
  56. }
  57. //fmt.Printf("Parse():tokens=%#v\n", tokens)
  58. if len(tokens) == 0 { // ie. empty or whitespace-only input
  59. return nil, ParseError{
  60. code: ParseErrorCodeNothing,
  61. }
  62. }
  63. bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
  64. if err == nil { // it was a builtin
  65. return bltn, nil
  66. }
  67. if e, ok := err.(builtin.ParseError); ok {
  68. switch e.Code {
  69. case builtin.ParseErrorCodeUsage:
  70. return nil, err
  71. case builtin.ParseErrorCodeUnknownBuiltin:
  72. exec_path, err := exec.LookPath(tokens[0])
  73. if err != nil { // command not found
  74. return nil, ParseError{
  75. code: ParseErrorCodeNotFound,
  76. value: tokens[0],
  77. }
  78. }
  79. return ExternalCommand{
  80. ExecPath: exec_path,
  81. Name: tokens[0],
  82. Args: tokens[1:],
  83. }, nil
  84. }
  85. }
  86. panic("unexpected builtin.Parse(); only builtin.ParseError is expected")
  87. }