Explorar el Código

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 hace 10 meses
padre
commit
7bafc70477
Se han modificado 4 ficheros con 66 adiciones y 29 borrados
  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 Ver fichero

@@ -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 Ver fichero

@@ -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 Ver fichero

@@ -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 Ver fichero

@@ -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
+}