瀏覽代碼

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
 import "github.com/codecrafters-io/shell-starter-go/app/core"
7
 import "github.com/codecrafters-io/shell-starter-go/app/core"
8
 
8
 
9
 type Builtin interface {
9
 type Builtin interface {
10
-	Run(ctx *core.Context) core.ExitStatus
10
+	Run(ctx *core.Context) core.ActionResult
11
 	ParseArgs(ctx *core.Context, args []string) error
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
 func Parse(ctx *core.Context, name string, args []string) (Builtin, error) {
21
 func Parse(ctx *core.Context, name string, args []string) (Builtin, error) {
15
 	selected, err := selectBuiltin(name)
22
 	selected, err := selectBuiltin(name)
16
 	if err != nil {
23
 	if err != nil {
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
 		err := os.Chdir(path)
103
 		err := os.Chdir(path)
97
 		if err != nil {
104
 		if err != nil {
98
 			fmt.Fprintf(ctx.Stderr, "cd: %s: No such file or directory\n", path)
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
 	if self.path == "~" || self.path == "" {
110
 	if self.path == "~" || self.path == "" {
104
 		home_path := os.Getenv("HOME")
111
 		home_path := os.Getenv("HOME")
105
 		if len(home_path) == 0 {
112
 		if len(home_path) == 0 {
106
 			fmt.Fprintln(ctx.Stderr, "error: $HOME environment variable is empty or unset")
113
 			fmt.Fprintln(ctx.Stderr, "error: $HOME environment variable is empty or unset")
107
-			return 4
114
+			return wrapEs(4)
108
 		}
115
 		}
109
 		return chdir(home_path)
116
 		return chdir(home_path)
110
 	}
117
 	}
124
 	return nil
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
 	fmt.Fprintln(ctx.Stdout, strings.Join(self.args, " "))
135
 	fmt.Fprintln(ctx.Stdout, strings.Join(self.args, " "))
129
-	return 0
136
+	return wrapEs(0)
130
 }
137
 }
131
 
138
 
132
 //
139
 //
143
 	return nil
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
 	os.Exit(0)
154
 	os.Exit(0)
148
-	return 0
155
+	return wrapEs(0)
149
 }
156
 }
150
 
157
 
151
 //
158
 //
162
 	return nil
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
 	cwd_path, err := os.Getwd()
173
 	cwd_path, err := os.Getwd()
167
 	if err != nil {
174
 	if err != nil {
168
 		panic("error getting current working directory")
175
 		panic("error getting current working directory")
169
 	}
176
 	}
170
 	fmt.Fprintln(ctx.Stdout, cwd_path)
177
 	fmt.Fprintln(ctx.Stdout, cwd_path)
171
-	return 0
178
+	return wrapEs(0)
172
 }
179
 }
173
 
180
 
174
 //
181
 //
187
 	return nil
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
 	_, err := selectBuiltin(self.query)
198
 	_, err := selectBuiltin(self.query)
192
 	if err == nil {
199
 	if err == nil {
193
 		fmt.Fprintf(ctx.Stdout, "%s is a shell builtin\n", self.query)
200
 		fmt.Fprintf(ctx.Stdout, "%s is a shell builtin\n", self.query)
194
-		return 0
201
+		return wrapEs(0)
195
 	}
202
 	}
196
 	exec_path, err := exec.LookPath(self.query)
203
 	exec_path, err := exec.LookPath(self.query)
197
 	if err == nil {
204
 	if err == nil {
198
 		fmt.Fprintf(ctx.Stdout, "%s is %s\n", self.query, exec_path)
205
 		fmt.Fprintf(ctx.Stdout, "%s is %s\n", self.query, exec_path)
199
-		return 0
206
+		return wrapEs(0)
200
 	}
207
 	}
201
 	fmt.Fprintf(ctx.Stderr, "%s: not found\n", self.query)
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
 		Stdin:  os.Stdin,
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
 			continue
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
 import "github.com/codecrafters-io/shell-starter-go/app/builtin"
8
 import "github.com/codecrafters-io/shell-starter-go/app/builtin"
9
 
9
 
10
 type Runnable interface {
10
 type Runnable interface {
11
-	Run(ctx *core.Context) core.ExitStatus
11
+	Run(ctx *core.Context) core.ActionResult
12
 }
12
 }
13
 
13
 
14
 type ExternalCommand struct {
14
 type ExternalCommand struct {
17
 	Name     string
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
 	cmd := exec.Command(self.ExecPath, self.Args...)
27
 	cmd := exec.Command(self.ExecPath, self.Args...)
22
 	cmd.Args[0] = self.Name
28
 	cmd.Args[0] = self.Name
23
 	cmd.Stdout = ctx.Stdout
29
 	cmd.Stdout = ctx.Stdout
26
 	err := cmd.Run()
32
 	err := cmd.Run()
27
 	if err != nil {
33
 	if err != nil {
28
 		if exiterr, ok := err.(*exec.ExitError); ok {
34
 		if exiterr, ok := err.(*exec.ExitError); ok {
29
-			return core.ExitStatus(exiterr.ExitCode())
35
+			return wrapEs(exiterr.ExitCode())
30
 			// fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode())
36
 			// fmt.Printf("Handle():exiterr=%#v\n", exiterr.ExitCode())
31
 		} else {
37
 		} else {
32
 			fmt.Printf("Handle():err=%#v\n", err)
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
 type ParseError struct {
45
 type ParseError struct {
43
 type ParseErrorCode int
49
 type ParseErrorCode int
44
 
50
 
45
 const (
51
 const (
46
-	ParseErrorCodeNothing ParseErrorCode = iota
47
-	ParseErrorCodeNotFound
52
+	ParseErrorCodeNotFound ParseErrorCode = iota
48
 )
53
 )
49
 
54
 
50
 func (e ParseError) Error() string {
55
 func (e ParseError) Error() string {
51
 	switch e.code {
56
 	switch e.code {
52
 	case ParseErrorCodeNotFound:
57
 	case ParseErrorCodeNotFound:
53
 		return fmt.Sprintf("%s: not found", e.value)
58
 		return fmt.Sprintf("%s: not found", e.value)
54
-	case ParseErrorCodeNothing:
55
-		return ""
56
 	default:
59
 	default:
57
 		return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value)
60
 		return fmt.Sprintf("unknown ParseErrorCode code: %d .value=%q", e.code, e.value)
58
 	}
61
 	}
66
 	}
69
 	}
67
 	//fmt.Printf("Parse():tokens=%#v\n", tokens)
70
 	//fmt.Printf("Parse():tokens=%#v\n", tokens)
68
 	if len(tokens) == 0 { // ie. empty or whitespace-only input
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
 	bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
74
 	bltn, err := builtin.Parse(ctx, tokens[0], tokens[1:])
74
 	if err == nil { // it was a builtin
75
 	if err == nil { // it was a builtin
95
 	}
96
 	}
96
 	panic("unexpected builtin.Parse(); only builtin.ParseError is expected")
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
+}