123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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.ActionResult
  9. }
  10. type ExternalCommand struct {
  11. Args []string
  12. ExecPath string
  13. Name string
  14. }
  15. func (self ExternalCommand) Run(ctx *core.Context) core.ActionResult {
  16. wrapEs := func(es int) core.ActionResult {
  17. return core.ActionResult{
  18. Code: core.ActionResultCodeExternalCommand,
  19. ExitStatus: core.ExitStatus(es),
  20. }
  21. }
  22. cmd := exec.Command(self.ExecPath, self.Args...)
  23. cmd.Args[0] = self.Name
  24. cmd.Stdout = ctx.Stdout
  25. cmd.Stderr = ctx.Stderr
  26. cmd.Stdin = ctx.Stdin
  27. err := cmd.Run()
  28. if err != nil {
  29. if exiterr, ok := err.(*exec.ExitError); ok {
  30. return wrapEs(exiterr.ExitCode())
  31. // fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode())
  32. } else {
  33. fmt.Printf("Handle():err=%#v\n", err)
  34. return wrapEs(65535)
  35. }
  36. }
  37. return wrapEs(0)
  38. }
  39. type ParseError struct {
  40. code ParseErrorCode
  41. value string
  42. }
  43. type ParseErrorCode int
  44. const (
  45. ParseErrorCodeNotFound ParseErrorCode = iota
  46. )
  47. func (e ParseError) Error() string {
  48. switch e.code {
  49. case ParseErrorCodeNotFound:
  50. return fmt.Sprintf("%s: not found", e.value)
  51. default:
  52. return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value)
  53. }
  54. }
  55. func Parse(ctx *core.Context, command_line string) (Runnable, error) {
  56. tokens, err := tokenize.Tokenize(command_line)
  57. if err != nil {
  58. return nil, err
  59. }
  60. //fmt.Printf("Parse():tokens=%#v\n", tokens)
  61. if len(tokens) == 0 { // ie. empty or whitespace-only input
  62. return Noop{}, nil
  63. }
  64. bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
  65. if err == nil { // it was a builtin
  66. return bltn, nil
  67. }
  68. if e, ok := err.(builtin.ParseError); ok {
  69. switch e.Code {
  70. case builtin.ParseErrorCodeUsage:
  71. return nil, err
  72. case builtin.ParseErrorCodeUnknownBuiltin:
  73. exec_path, err := exec.LookPath(tokens[0])
  74. if err != nil { // command not found
  75. return nil, ParseError{
  76. code: ParseErrorCodeNotFound,
  77. value: tokens[0],
  78. }
  79. }
  80. return ExternalCommand{
  81. ExecPath: exec_path,
  82. Name: tokens[0],
  83. Args: tokens[1:],
  84. }, nil
  85. }
  86. }
  87. panic("unexpected builtin.Parse(); only builtin.ParseError is expected")
  88. }
  89. type Noop struct{}
  90. func (self Noop) Run(ctx *core.Context) core.ActionResult {
  91. return core.ActionResult{
  92. Code: core.ActionResultCodeNoop,
  93. ExitStatus: 0,
  94. }
  95. }