소스 검색

Wrap Runnable.Run()'s ExitStatus in a more general ActionResult

This helps us get rid of ParseError abuse and thus fix undesired stderr
exit on every empty REPL loop.
Alois Mahdal 3 일 전
부모
커밋
7bafc70477
4개의 변경된 파일66개의 추가작업 그리고 29개의 파일을 삭제
  1. 23
    16
      app/builtin/builtin.go
  2. 12
    0
      app/core/core.go
  3. 9
    1
      app/main.go
  4. 22
    12
      app/runnable/runnable.go

+ 23
- 16
app/builtin/builtin.go 파일 보기

@@ -7,10 +7,17 @@ import "strings"
7 7
 import "github.com/codecrafters-io/shell-starter-go/app/core"
8 8
 
9 9
 type Builtin interface {
10
-	Run(ctx *core.Context) core.ExitStatus
10
+	Run(ctx *core.Context) core.ActionResult
11 11
 	ParseArgs(ctx *core.Context, args []string) error
12 12
 }
13 13
 
14
+func wrapEs(es int) core.ActionResult {
15
+	return core.ActionResult{
16
+		Code:       core.ActionResultCodeBuiltinCommand,
17
+		ExitStatus: core.ExitStatus(es),
18
+	}
19
+}
20
+
14 21
 func Parse(ctx *core.Context, name string, args []string) (Builtin, error) {
15 22
 	selected, err := selectBuiltin(name)
16 23
 	if err != nil {
@@ -91,20 +98,20 @@ func (self *CdBuiltin) ParseArgs(ctx *core.Context, args []string) error {
91 98
 	}
92 99
 }
93 100
 
94
-func (self *CdBuiltin) Run(ctx *core.Context) core.ExitStatus {
95
-	chdir := func(path string) core.ExitStatus {
101
+func (self *CdBuiltin) Run(ctx *core.Context) core.ActionResult {
102
+	chdir := func(path string) core.ActionResult {
96 103
 		err := os.Chdir(path)
97 104
 		if err != nil {
98 105
 			fmt.Fprintf(ctx.Stderr, "cd: %s: No such file or directory\n", path)
99
-			return 4
106
+			return wrapEs(4)
100 107
 		}
101
-		return 0
108
+		return wrapEs(0)
102 109
 	}
103 110
 	if self.path == "~" || self.path == "" {
104 111
 		home_path := os.Getenv("HOME")
105 112
 		if len(home_path) == 0 {
106 113
 			fmt.Fprintln(ctx.Stderr, "error: $HOME environment variable is empty or unset")
107
-			return 4
114
+			return wrapEs(4)
108 115
 		}
109 116
 		return chdir(home_path)
110 117
 	}
@@ -124,9 +131,9 @@ func (self *EchoBuiltin) ParseArgs(ctx *core.Context, args []string) error {
124 131
 	return nil
125 132
 }
126 133
 
127
-func (self *EchoBuiltin) Run(ctx *core.Context) core.ExitStatus {
134
+func (self *EchoBuiltin) Run(ctx *core.Context) core.ActionResult {
128 135
 	fmt.Fprintln(ctx.Stdout, strings.Join(self.args, " "))
129
-	return 0
136
+	return wrapEs(0)
130 137
 }
131 138
 
132 139
 //
@@ -143,9 +150,9 @@ func (self *ExitBuiltin) ParseArgs(ctx *core.Context, args []string) error {
143 150
 	return nil
144 151
 }
145 152
 
146
-func (self *ExitBuiltin) Run(ctx *core.Context) core.ExitStatus {
153
+func (self *ExitBuiltin) Run(ctx *core.Context) core.ActionResult {
147 154
 	os.Exit(0)
148
-	return 0
155
+	return wrapEs(0)
149 156
 }
150 157
 
151 158
 //
@@ -162,13 +169,13 @@ func (self *PwdBuiltin) ParseArgs(ctx *core.Context, args []string) error {
162 169
 	return nil
163 170
 }
164 171
 
165
-func (self *PwdBuiltin) Run(ctx *core.Context) core.ExitStatus {
172
+func (self *PwdBuiltin) Run(ctx *core.Context) core.ActionResult {
166 173
 	cwd_path, err := os.Getwd()
167 174
 	if err != nil {
168 175
 		panic("error getting current working directory")
169 176
 	}
170 177
 	fmt.Fprintln(ctx.Stdout, cwd_path)
171
-	return 0
178
+	return wrapEs(0)
172 179
 }
173 180
 
174 181
 //
@@ -187,17 +194,17 @@ func (self *TypeBuiltin) ParseArgs(ctx *core.Context, args []string) error {
187 194
 	return nil
188 195
 }
189 196
 
190
-func (self *TypeBuiltin) Run(ctx *core.Context) core.ExitStatus {
197
+func (self *TypeBuiltin) Run(ctx *core.Context) core.ActionResult {
191 198
 	_, err := selectBuiltin(self.query)
192 199
 	if err == nil {
193 200
 		fmt.Fprintf(ctx.Stdout, "%s is a shell builtin\n", self.query)
194
-		return 0
201
+		return wrapEs(0)
195 202
 	}
196 203
 	exec_path, err := exec.LookPath(self.query)
197 204
 	if err == nil {
198 205
 		fmt.Fprintf(ctx.Stdout, "%s is %s\n", self.query, exec_path)
199
-		return 0
206
+		return wrapEs(0)
200 207
 	}
201 208
 	fmt.Fprintf(ctx.Stderr, "%s: not found\n", self.query)
202
-	return 0
209
+	return wrapEs(0)
203 210
 }

+ 12
- 0
app/core/core.go 파일 보기

@@ -18,3 +18,15 @@ func MakeContext() Context {
18 18
 		Stdin:  os.Stdin,
19 19
 	}
20 20
 }
21
+
22
+type ActionResult struct {
23
+	Code       ActionResultCode
24
+	ExitStatus ExitStatus
25
+}
26
+type ActionResultCode uint8
27
+
28
+const (
29
+	ActionResultCodeNoop ActionResultCode = iota
30
+	ActionResultCodeBuiltinCommand
31
+	ActionResultCodeExternalCommand
32
+)

+ 9
- 1
app/main.go 파일 보기

@@ -33,7 +33,15 @@ func repl(ctx *core.Context) {
33 33
 			continue
34 34
 		}
35 35
 
36
-		runnable.Run(ctx)
36
+		result := runnable.Run(ctx)
37
+		switch result.Code {
38
+		case core.ActionResultCodeNoop:
39
+		case core.ActionResultCodeBuiltinCommand:
40
+		case core.ActionResultCodeExternalCommand:
41
+		default:
42
+			fmt.Fprintf(os.Stderr, "unknown action result code input: %d", result.Code)
43
+			os.Exit(15)
44
+		}
37 45
 	}
38 46
 }
39 47
 

+ 22
- 12
app/runnable/runnable.go 파일 보기

@@ -8,7 +8,7 @@ import "github.com/codecrafters-io/shell-starter-go/app/tokenize"
8 8
 import "github.com/codecrafters-io/shell-starter-go/app/builtin"
9 9
 
10 10
 type Runnable interface {
11
-	Run(ctx *core.Context) core.ExitStatus
11
+	Run(ctx *core.Context) core.ActionResult
12 12
 }
13 13
 
14 14
 type ExternalCommand struct {
@@ -17,7 +17,13 @@ type ExternalCommand struct {
17 17
 	Name     string
18 18
 }
19 19
 
20
-func (self ExternalCommand) Run(ctx *core.Context) core.ExitStatus {
20
+func (self ExternalCommand) Run(ctx *core.Context) core.ActionResult {
21
+	wrapEs := func(es int) core.ActionResult {
22
+		return core.ActionResult{
23
+			Code:       core.ActionResultCodeExternalCommand,
24
+			ExitStatus: core.ExitStatus(es),
25
+		}
26
+	}
21 27
 	cmd := exec.Command(self.ExecPath, self.Args...)
22 28
 	cmd.Args[0] = self.Name
23 29
 	cmd.Stdout = ctx.Stdout
@@ -26,14 +32,14 @@ func (self ExternalCommand) Run(ctx *core.Context) core.ExitStatus {
26 32
 	err := cmd.Run()
27 33
 	if err != nil {
28 34
 		if exiterr, ok := err.(*exec.ExitError); ok {
29
-			return core.ExitStatus(exiterr.ExitCode())
35
+			return wrapEs(exiterr.ExitCode())
30 36
 			// fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode())
31 37
 		} else {
32 38
 			fmt.Printf("Handle():err=%#v\n", err)
33
-			return core.ExitStatus(65535)
39
+			return wrapEs(65535)
34 40
 		}
35 41
 	}
36
-	return 0
42
+	return wrapEs(0)
37 43
 }
38 44
 
39 45
 type ParseError struct {
@@ -43,16 +49,13 @@ type ParseError struct {
43 49
 type ParseErrorCode int
44 50
 
45 51
 const (
46
-	ParseErrorCodeNothing ParseErrorCode = iota
47
-	ParseErrorCodeNotFound
52
+	ParseErrorCodeNotFound ParseErrorCode = iota
48 53
 )
49 54
 
50 55
 func (e ParseError) Error() string {
51 56
 	switch e.code {
52 57
 	case ParseErrorCodeNotFound:
53 58
 		return fmt.Sprintf("%s: not found", e.value)
54
-	case ParseErrorCodeNothing:
55
-		return ""
56 59
 	default:
57 60
 		return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value)
58 61
 	}
@@ -66,9 +69,7 @@ func Parse(ctx *core.Context, command_line string) (Runnable, error) {
66 69
 	}
67 70
 	//fmt.Printf("Parse():tokens=%#v\n", tokens)
68 71
 	if len(tokens) == 0 { // ie. empty or whitespace-only input
69
-		return nil, ParseError{
70
-			code: ParseErrorCodeNothing,
71
-		}
72
+		return Noop{}, nil
72 73
 	}
73 74
 	bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
74 75
 	if err == nil { // it was a builtin
@@ -95,3 +96,12 @@ func Parse(ctx *core.Context, command_line string) (Runnable, error) {
95 96
 	}
96 97
 	panic("unexpected builtin.Parse(); only builtin.ParseError is expected")
97 98
 }
99
+
100
+type Noop struct{}
101
+
102
+func (self Noop) Run(ctx *core.Context) core.ActionResult {
103
+	return core.ActionResult{
104
+		Code:       core.ActionResultCodeNoop,
105
+		ExitStatus: 0,
106
+	}
107
+}