瀏覽代碼

Implement #MG5

Alois Mahdal 1 周之前
父節點
當前提交
6381f2ce6b
共有 1 個文件被更改,包括 91 次插入20 次删除
  1. 91
    20
      app/main.go

+ 91
- 20
app/main.go 查看文件

8
 	"strings"
8
 	"strings"
9
 )
9
 )
10
 
10
 
11
+func getenv(key string, fallback string) string {
12
+	value, ok := os.LookupEnv(key)
13
+	if ok {
14
+		return value
15
+	} else {
16
+		return fallback
17
+	}
18
+}
19
+
11
 type Context struct {
20
 type Context struct {
12
 	stdout io.Writer
21
 	stdout io.Writer
13
 	stderr io.Writer
22
 	stderr io.Writer
14
 	stdin  io.Reader
23
 	stdin  io.Reader
24
+	paths  []string
25
+}
26
+
27
+func makeContext() Context {
28
+	path_str := getenv("PATH", "")
29
+	paths := make([]string, 0)
30
+	for elem := range strings.SplitSeq(path_str, ":") {
31
+		if elem == "" {
32
+			continue
33
+		}
34
+		if !strings.HasPrefix(elem, "/") {
35
+			continue
36
+		}
37
+		paths = append(paths, elem)
38
+	}
39
+	return Context{
40
+		stdout: os.Stdout,
41
+		stderr: os.Stderr,
42
+		stdin:  os.Stdin,
43
+		paths:  paths,
44
+	}
15
 }
45
 }
16
 
46
 
17
 type GetCommandResult int
47
 type GetCommandResult int
24
 type Command struct {
54
 type Command struct {
25
 	command_type CommandType
55
 	command_type CommandType
26
 	tokens       []string
56
 	tokens       []string
57
+	exec_path    string
27
 }
58
 }
28
 
59
 
29
 func makeCommandNone() Command {
60
 func makeCommandNone() Command {
30
 	return Command{
61
 	return Command{
31
 		command_type: CommandTypeNone,
62
 		command_type: CommandTypeNone,
32
 		tokens:       nil,
63
 		tokens:       nil,
64
+		exec_path:    "",
33
 	}
65
 	}
34
 }
66
 }
35
 
67
 
36
-func makeCommand(command_type CommandType, tokens []string) Command {
37
-	if command_type == CommandTypeNone {
38
-		return Command{
39
-			command_type: command_type,
40
-			tokens:       nil,
41
-		}
42
-	} else {
68
+func makeCommandBuiltin(command_type CommandType, tokens []string) Command {
69
+	switch command_type {
70
+	case CommandTypeNone:
71
+		panic("invalid usage")
72
+	case CommandTypeExternal:
73
+		panic("invalid usage")
74
+	default:
43
 		return Command{
75
 		return Command{
44
 			command_type: command_type,
76
 			command_type: command_type,
45
 			tokens:       tokens,
77
 			tokens:       tokens,
47
 	}
79
 	}
48
 }
80
 }
49
 
81
 
82
+func makeCommandExternal(tokens []string, exec_path string) Command {
83
+	return Command{
84
+		command_type: CommandTypeExternal,
85
+		tokens:       tokens,
86
+		exec_path:    exec_path,
87
+	}
88
+}
89
+
50
 type CommandType int
90
 type CommandType int
51
 
91
 
52
 const (
92
 const (
63
 	if err != nil {
103
 	if err != nil {
64
 		return makeCommandNone(), err
104
 		return makeCommandNone(), err
65
 	}
105
 	}
66
-	command, err := parseCommand(command_line)
106
+	command, err := parseCommand(command_line, ctx.paths)
107
+	// fmt.Printf("getCommand():command=%#v\n", command)
67
 	if err != nil {
108
 	if err != nil {
68
 		fmt.Fprintln(ctx.stderr, err)
109
 		fmt.Fprintln(ctx.stderr, err)
69
 		return makeCommandNone(), nil
110
 		return makeCommandNone(), nil
76
 	return tokens
117
 	return tokens
77
 }
118
 }
78
 
119
 
79
-func parseCommand(command_line string) (Command, error) {
120
+type ParseCommandError struct {
121
+	code  ParseCommandErrorCode
122
+	value string
123
+}
124
+type ParseCommandErrorCode int
125
+
126
+const (
127
+	ParseCommandErrorCodeNotFound ParseCommandErrorCode = iota
128
+)
129
+
130
+func (e ParseCommandError) Error() string {
131
+	switch e.code {
132
+	case ParseCommandErrorCodeNotFound:
133
+		return fmt.Sprintf("%s: not found", e.value)
134
+	default:
135
+		return fmt.Sprintf("unknown ParseCommandErrorCode code: %d .value=%q", e.code, e.value)
136
+	}
137
+}
138
+
139
+func parseCommand(command_line string, paths []string) (Command, error) {
80
 	tokens := tokenize(command_line)
140
 	tokens := tokenize(command_line)
81
 	if len(tokens) == 0 {
141
 	if len(tokens) == 0 {
82
 		return makeCommandNone(), nil
142
 		return makeCommandNone(), nil
83
 	}
143
 	}
84
 	if tokens[0] == "exit" {
144
 	if tokens[0] == "exit" {
85
-		return makeCommand(CommandTypeBuiltinExit, tokens), nil
145
+		return makeCommandBuiltin(CommandTypeBuiltinExit, tokens), nil
86
 	}
146
 	}
87
 	if tokens[0] == "echo" {
147
 	if tokens[0] == "echo" {
88
-		return makeCommand(CommandTypeBuiltinEcho, tokens), nil
148
+		return makeCommandBuiltin(CommandTypeBuiltinEcho, tokens), nil
89
 	}
149
 	}
90
 	if tokens[0] == "type" {
150
 	if tokens[0] == "type" {
91
-		return makeCommand(CommandTypeBuiltinType, tokens), nil
151
+		return makeCommandBuiltin(CommandTypeBuiltinType, tokens), nil
92
 	}
152
 	}
93
-	return makeCommandNone(), fmt.Errorf("%s: not found", tokens[0])
153
+	for _, path := range paths {
154
+		full_path := fmt.Sprintf("%s/%s", path, tokens[0])
155
+		// fmt.Printf("parseCommand():full_path=%#v\n", full_path)
156
+		stat, err := os.Stat(full_path)
157
+		if err != nil {
158
+			continue
159
+		}
160
+		if stat.IsDir() {
161
+			continue
162
+		}
163
+		if stat.Mode()&0100 == 0 {
164
+			continue
165
+		}
166
+		return makeCommandExternal(tokens, full_path), nil
167
+	}
168
+	return makeCommandNone(), ParseCommandError{ParseCommandErrorCodeNotFound, tokens[0]}
94
 }
169
 }
95
 
170
 
96
 func handleCommand(ctx Context, command Command) {
171
 func handleCommand(ctx Context, command Command) {
107
 			fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
182
 			fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
108
 			return
183
 			return
109
 		}
184
 		}
110
-		parsed, err := parseCommand(command.tokens[1])
185
+		parsed, err := parseCommand(command.tokens[1], ctx.paths)
111
 		if err != nil {
186
 		if err != nil {
112
 			fmt.Fprintln(ctx.stderr, err)
187
 			fmt.Fprintln(ctx.stderr, err)
113
 			return
188
 			return
116
 		case CommandTypeNone:
191
 		case CommandTypeNone:
117
 			panic("impossible parseCommand() result")
192
 			panic("impossible parseCommand() result")
118
 		case CommandTypeExternal:
193
 		case CommandTypeExternal:
119
-			fmt.Fprintf(ctx.stdout, "%s is an external command\n", parsed.tokens[0])
194
+			fmt.Fprintf(ctx.stdout, "%s is %s\n", parsed.tokens[0], parsed.exec_path)
120
 		default:
195
 		default:
121
 			fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0])
196
 			fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0])
122
 		}
197
 		}
124
 }
199
 }
125
 
200
 
126
 func main() {
201
 func main() {
127
-	ctx := Context{
128
-		stdout: os.Stdout,
129
-		stderr: os.Stderr,
130
-		stdin:  os.Stdin,
131
-	}
202
+	ctx := makeContext()
132
 	for {
203
 	for {
133
 		command, err := getCommand(&ctx)
204
 		command, err := getCommand(&ctx)
134
 		if err != nil {
205
 		if err != nil {