| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- package main
-
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "os/exec"
- "strings"
- )
-
- type ExitStatus uint16
-
- type Context struct {
- stdout io.Writer
- stderr io.Writer
- stdin io.Reader
- }
-
- func makeContext() Context {
- return Context{
- stdout: os.Stdout,
- stderr: os.Stderr,
- stdin: os.Stdin,
- }
- }
-
- type GetCommandResult int
-
- const (
- GetCommandResultOk GetCommandResult = iota
- GetCommandResultError
- )
-
- type Command struct {
- command_type CommandType
- tokens []string
- exec_path string
- }
-
- func makeCommandNone() Command {
- return Command{
- command_type: CommandTypeNone,
- tokens: nil,
- exec_path: "",
- }
- }
-
- func makeCommandBuiltin(command_type CommandType, tokens []string) Command {
- switch command_type {
- case CommandTypeNone:
- panic("invalid usage")
- case CommandTypeExternal:
- panic("invalid usage")
- default:
- return Command{
- command_type: command_type,
- tokens: tokens,
- }
- }
- }
-
- func makeCommandExternal(tokens []string, exec_path string) Command {
- return Command{
- command_type: CommandTypeExternal,
- tokens: tokens,
- exec_path: exec_path,
- }
- }
-
- type CommandType int
-
- const (
- CommandTypeNone CommandType = iota
- CommandTypeExternal
- CommandTypeBuiltinExit
- CommandTypeBuiltinEcho
- CommandTypeBuiltinType
- )
-
- func getCommand(ctx *Context) (Command, error) {
- fmt.Print("$ ")
- command_line, err := bufio.NewReader(ctx.stdin).ReadString('\n')
- if err != nil {
- return makeCommandNone(), err
- }
- command, err := parseCommand(command_line)
- // fmt.Printf("getCommand():command=%#v\n", command)
- if err != nil {
- fmt.Fprintln(ctx.stderr, err)
- return makeCommandNone(), nil
- }
- return command, nil
- }
-
- func tokenize(str string) []string {
- tokens := strings.Fields(str)
- return tokens
- }
-
- type ParseCommandError struct {
- code ParseCommandErrorCode
- value string
- }
- type ParseCommandErrorCode int
-
- const (
- ParseCommandErrorCodeNotFound ParseCommandErrorCode = iota
- )
-
- func (e ParseCommandError) Error() string {
- switch e.code {
- case ParseCommandErrorCodeNotFound:
- return fmt.Sprintf("%s: not found", e.value)
- default:
- return fmt.Sprintf("unknown ParseCommandErrorCode code: %d .value=%q", e.code, e.value)
- }
- }
-
- func parseCommand(command_line string) (Command, error) {
- tokens := tokenize(command_line)
- if len(tokens) == 0 {
- return makeCommandNone(), nil
- }
- if tokens[0] == "exit" {
- return makeCommandBuiltin(CommandTypeBuiltinExit, tokens), nil
- }
- if tokens[0] == "echo" {
- return makeCommandBuiltin(CommandTypeBuiltinEcho, tokens), nil
- }
- if tokens[0] == "type" {
- return makeCommandBuiltin(CommandTypeBuiltinType, tokens), nil
- }
- exec_path, err := exec.LookPath(tokens[0])
- if err != nil {
- return makeCommandNone(), ParseCommandError{ParseCommandErrorCodeNotFound, tokens[0]}
- }
- return makeCommandExternal(tokens, exec_path), nil
- }
-
- func handleCommand(ctx Context, command Command) ExitStatus {
- // fmt.Printf("handleCommand():command=%#v\n", command)
- switch command.command_type {
- case CommandTypeNone:
- return 0
- case CommandTypeBuiltinExit:
- os.Exit(0)
- case CommandTypeBuiltinEcho:
- fmt.Fprintln(ctx.stdout, strings.Join(command.tokens[1:], " "))
- case CommandTypeBuiltinType:
- if len(command.tokens) != 2 {
- fmt.Fprintln(ctx.stderr, "usage: type COMMAND")
- return 2
- }
- parsed, err := parseCommand(command.tokens[1])
- if err != nil {
- fmt.Fprintln(ctx.stderr, err)
- return 0
- }
- switch parsed.command_type {
- case CommandTypeNone:
- panic("impossible parseCommand() result")
- case CommandTypeExternal:
- fmt.Fprintf(ctx.stdout, "%s is %s\n", parsed.tokens[0], parsed.exec_path)
- default:
- fmt.Fprintf(ctx.stdout, "%s is a shell builtin\n", parsed.tokens[0])
- }
- case CommandTypeExternal:
- cmd := exec.Command(command.exec_path, command.tokens[1:]...)
- cmd.Args = command.tokens
- cmd.Stdout = ctx.stdout
- cmd.Stderr = ctx.stderr
- cmd.Stdin = ctx.stdin
- err := cmd.Run()
- if err != nil {
- if exiterr, ok := err.(*exec.ExitError); ok {
- return ExitStatus(exiterr.ExitCode())
- // fmt.Printf("handleCommand():exiterr=%#v\n", exiterr.ExitCode())
- } else {
- fmt.Printf("handleCommand():err=%#v\n", err)
- return ExitStatus(65535)
- }
- }
- return ExitStatus(0)
- default:
- panic("unknown command type")
- }
- return ExitStatus(65535)
- }
-
- func main() {
- ctx := makeContext()
- for {
- command, err := getCommand(&ctx)
- if err != nil {
- fmt.Fprintln(os.Stderr, "Error reading input: ", err)
- os.Exit(1)
- }
- _ = handleCommand(ctx, command)
- }
- }
|