diff --git a/.gitignore b/.gitignore index adf8f72..436ccf1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ # Go workspace file go.work +# Output of go tooling +shell diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dedc6b1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.futureblog.eu/andre/shell + +go 1.21.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..f4de8ec --- /dev/null +++ b/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" + "unicode" +) + +func main() { + // Load the configuration file, if any + + // Run command loop + myshell_loop() + + // Perform any shutdown/cleanup +} + +func myshell_loop() { + for { + // Read command + fmt.Printf("myshell> ") + line := read_line() + + // Parse command + args := split_line(line) + + // Execute command + output, err := execute(args) + if err != nil { + fmt.Println("error during exec command:", err) + } else { + fmt.Printf(string(output)) + } + } + // Loop back to read command +} + +func read_line() string { + // Read the command line + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + fmt.Println("Error reading command line: ", err) + } + + // Remove the newline character + input = strings.TrimSuffix(input, "\r\n") + input = strings.TrimSuffix(input, "\n") + + return input +} + +// Split the line into arguments +func split_line(line string) []string { + fieldSeparator := func(c rune) bool { + return unicode.IsSpace(c) || c == '\a' + } + args := strings.FieldsFunc(line, fieldSeparator) + + return args +} + +var builtin_cmds = map[string]func([]string) ([]byte, error){ + "cd": myshell_cd, + "exit": myshell_exit, +} + +// Execute the command +func execute(args []string) ([]byte, error) { + // Check for builtin commands + if cmd, ok := builtin_cmds[args[0]]; ok { + return cmd(args) + } + + cmd := exec.Command("sh", "-c", strings.Join(args, " ")) + output, err := cmd.CombinedOutput() + return output, err +} + +func myshell_cd(args []string) ([]byte, error) { + if len(args) < 2 { + return nil, fmt.Errorf("cd: missing argument") + } + err := os.Chdir(args[1]) + return nil, err +} + +func myshell_exit(args []string) ([]byte, error) { + os.Exit(0) + return nil, nil +} \ No newline at end of file diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..d47fd4b --- /dev/null +++ b/main_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestSplitLine(t *testing.T) { + tests := []struct { + line string + want []string + }{ + {"", []string{}}, + {"arg1 arg2 arg3", []string{"arg1", "arg2", "arg3"}}, + {" arg1\targ2\narg3\rarg4\aarg5 ", []string{"arg1", "arg2", "arg3", "arg4", "arg5"}}, + {"\"quoted arg\" 'another arg'", []string{"\"quoted", "arg\"", "'another", "arg'"}}, + } + + for _, tt := range tests { + t.Run(tt.line, func(t *testing.T) { + if got := split_line(tt.line); !reflect.DeepEqual(got, tt.want) { + t.Errorf("split_line(%q) = %v, want %v", tt.line, got, tt.want) + } + }) + } +}