ソースを参照

Implement #MG5

Alois Mahdal 1 週間 前
コミット
6381f2ce6b
共有1 個のファイルを変更した91 個の追加20 個の削除を含む
  1. 91
    20
      app/main.go

+ 91
- 20
app/main.go ファイルの表示

@@ -8,10 +8,40 @@ import (
8 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 20
 type Context struct {
12 21
 	stdout io.Writer
13 22
 	stderr io.Writer
14 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 47
 type GetCommandResult int
@@ -24,22 +54,24 @@ const (
24 54
 type Command struct {
25 55
 	command_type CommandType
26 56
 	tokens       []string
57
+	exec_path    string
27 58
 }
28 59
 
29 60
 func makeCommandNone() Command {
30 61
 	return Command{
31 62
 		command_type: CommandTypeNone,
32 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 75
 		return Command{
44 76
 			command_type: command_type,
45 77
 			tokens:       tokens,
@@ -47,6 +79,14 @@ func makeCommand(command_type CommandType, tokens []string) Command {
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 90
 type CommandType int
51 91
 
52 92
 const (
@@ -63,7 +103,8 @@ func getCommand(ctx *Context) (Command, error) {
63 103
 	if err != nil {
64 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 108
 	if err != nil {
68 109
 		fmt.Fprintln(ctx.stderr, err)
69 110
 		return makeCommandNone(), nil
@@ -76,21 +117,55 @@ func tokenize(str string) []string {
76 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 140
 	tokens := tokenize(command_line)
81 141
 	if len(tokens) == 0 {
82 142
 		return makeCommandNone(), nil
83 143
 	}
84 144
 	if tokens[0] == "exit" {
85
-		return makeCommand(CommandTypeBuiltinExit, tokens), nil
145
+		return makeCommandBuiltin(CommandTypeBuiltinExit, tokens), nil
86 146
 	}
87 147
 	if tokens[0] == "echo" {
88
-		return makeCommand(CommandTypeBuiltinEcho, tokens), nil
148
+		return makeCommandBuiltin(CommandTypeBuiltinEcho, tokens), nil
89 149
 	}
90 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 171
 func handleCommand(ctx Context, command Command) {
@@ -107,7 +182,7 @@ func handleCommand(ctx Context, command Command) {
107 182
 			fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
108 183
 			return
109 184
 		}
110
-		parsed, err := parseCommand(command.tokens[1])
185
+		parsed, err := parseCommand(command.tokens[1], ctx.paths)
111 186
 		if err != nil {
112 187
 			fmt.Fprintln(ctx.stderr, err)
113 188
 			return
@@ -116,7 +191,7 @@ func handleCommand(ctx Context, command Command) {
116 191
 		case CommandTypeNone:
117 192
 			panic("impossible parseCommand() result")
118 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 195
 		default:
121 196
 			fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0])
122 197
 		}
@@ -124,11 +199,7 @@ func handleCommand(ctx Context, command Command) {
124 199
 }
125 200
 
126 201
 func main() {
127
-	ctx := Context{
128
-		stdout: os.Stdout,
129
-		stderr: os.Stderr,
130
-		stdin:  os.Stdin,
131
-	}
202
+	ctx := makeContext()
132 203
 	for {
133 204
 		command, err := getCommand(&ctx)
134 205
 		if err != nil {