package main import ( "errors" "os" "strconv" "strings" ) var ( ErrInvalidArgs = errors.New("bad command arguments") ErrUnknownCommand = errors.New("don't know that one") ) type Command struct { Name string Args []string } func ParseCommand(line string) (*Command, error) { line = strings.TrimSpace(line) if line == "" { return &Command{Name: "default"}, nil } cmd, rest, _ := strings.Cut(line, " ") switch line[0] { case 'a': if strings.HasPrefix("about", cmd) { return &Command{Name: "about"}, nil } case 'r': if strings.HasPrefix("root", cmd) { return &Command{Name: "root"}, nil } if strings.HasPrefix("reload", cmd) { return &Command{Name: "reload"}, nil } case 'R': if strings.HasPrefix("Root", cmd) { return &Command{Name: "Root"}, nil } case 'b': if strings.HasPrefix("back", cmd) { fields := strings.Fields(rest) switch len(fields) { case 0: return &Command{Name: "back"}, nil case 1: if _, err := strconv.Atoi(fields[0]); err != nil { return nil, ErrInvalidArgs } return &Command{Name: "back", Args: fields}, nil default: return nil, ErrInvalidArgs } } case 'f': if strings.HasPrefix("forward", cmd) { fields := strings.Fields(rest) switch len(fields) { case 0: return &Command{Name: "forward"}, nil case 1: if _, err := strconv.Atoi(fields[0]); err != nil { return nil, ErrInvalidArgs } return &Command{Name: "forward", Args: fields}, nil default: return nil, ErrInvalidArgs } } case 'i': if strings.HasPrefix("identity", cmd) { args, err := parseIdentityArgs(rest) if err != nil { return nil, err } return &Command{Name: "identity", Args: args}, nil } case 'n': if strings.HasPrefix("next", cmd) { return &Command{Name: "next"}, nil } case 'o': if strings.HasPrefix("outline", cmd) { return &Command{Name: "outline"}, nil } case 'p': if strings.HasPrefix("print", cmd) { return &Command{Name: "print"}, nil } if strings.HasPrefix("previous", cmd) { return &Command{Name: "previous"}, nil } if strings.HasPrefix("pipe", cmd) { return &Command{ Name: "pipe", Args: []string{rest}, }, nil } case 'u': if strings.HasPrefix("up", cmd) { return &Command{Name: "up"}, nil } case 'g': if strings.HasPrefix("go", cmd) { if rest == "" { return nil, ErrInvalidArgs } return &Command{Name: "go", Args: []string{rest}}, nil } case 'h': if strings.HasPrefix("help", cmd) { return &Command{ Name: "help", Args: []string{rest}, }, nil } if strings.HasPrefix("history", cmd) { return &Command{Name: "history"}, nil } case '|': return &Command{ Name: "pipe", Args: []string{strings.TrimSpace(line[1:])}, }, nil case '?': return &Command{Name: "help", Args: []string{""}}, nil case 's': if strings.HasPrefix("save", cmd) { return &Command{ Name: "save", Args: []string{rest}, }, nil } case 'l': if strings.HasPrefix("links", cmd) { return &Command{Name: "links"}, nil } case 'm': if strings.HasPrefix("mark", cmd) { args, err := parseMarkArgs(rest) if err != nil { return nil, err } return &Command{Name: "mark", Args: args}, nil } case 't': if strings.HasPrefix("tour", cmd) { args, err := parseTourArgs(rest) if err != nil { return nil, err } return &Command{Name: "tour", Args: args}, nil } case 'q': if strings.HasPrefix("quit", cmd) { return &Command{Name: "quit"}, nil } } if rest == "" { return &Command{Name: "go", Args: []string{cmd}}, nil } return nil, ErrUnknownCommand } func parseMarkArgs(line string) ([]string, error) { fields := strings.Fields(line) if len(fields) == 0 { return []string{"list"}, nil } switch fields[0][0] { case 'a': if strings.HasPrefix("add", fields[0]) { fields[0] = "add" if len(fields) != 3 { return nil, ErrInvalidArgs } return fields, nil } case 'g': if strings.HasPrefix("go", fields[0]) { fields[0] = "go" if len(fields) != 2 { return nil, ErrInvalidArgs } return fields, nil } case 'l': if strings.HasPrefix("list", fields[0]) { fields[0] = "list" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 'd': if strings.HasPrefix("delete", fields[0]) { fields[0] = "delete" if len(fields) != 2 { return nil, ErrInvalidArgs } return fields, nil } } if len(fields) != 1 { return nil, ErrInvalidArgs } return []string{"go", fields[0]}, nil } func parseTourArgs(line string) ([]string, error) { fields := strings.Fields(line) if len(fields) == 0 { return []string{"next"}, nil } switch fields[0][0] { case 'a': if strings.HasPrefix("add", fields[0]) { fields[0] = "add" if len(fields) == 1 { return nil, ErrInvalidArgs } if strings.HasPrefix("next", fields[1]) { if len(fields) == 2 { return nil, ErrInvalidArgs } fields[1] = "next" } return fields, nil } case 'c': if strings.HasPrefix("clear", fields[0]) { fields[0] = "clear" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 'n': if strings.HasPrefix("next", fields[0]) { fields[0] = "next" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 's': if strings.HasPrefix("select", fields[0]) { fields[0] = "select" if len(fields) == 1 { return append(fields, ""), nil } if len(fields) != 2 { return nil, ErrInvalidArgs } return fields, nil } if strings.HasPrefix("show", fields[0]) { fields[0] = "show" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 'l': if strings.HasPrefix("list", fields[0]) { fields[0] = "list" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 'p': if strings.HasPrefix("previous", fields[0]) { fields[0] = "previous" if len(fields) != 1 { return nil, ErrInvalidArgs } return fields, nil } case 'g': if strings.HasPrefix("go", fields[0]) { fields[0] = "go" if len(fields) != 2 { return nil, ErrInvalidArgs } if _, err := strconv.Atoi(fields[1]); err != nil { return nil, ErrInvalidArgs } return fields, nil } } return append([]string{"add"}, fields...), nil } func parseIdentityArgs(line string) ([]string, error) { fields := strings.Fields(line) if len(fields) == 0 { return []string{"list"}, nil } switch fields[0][0] { case 'c': if strings.HasPrefix("create", fields[0]) { fields[0] = "create" if len(fields) != 2 { return nil, ErrInvalidArgs } return fields, nil } case 'l': if strings.HasPrefix("list", fields[0]) { if len(fields) != 1 { return nil, ErrInvalidArgs } return []string{"list"}, nil } case 'd': if strings.HasPrefix("delete", fields[0]) { fields[0] = "delete" if len(fields) != 2 { return nil, ErrInvalidArgs } return fields, nil } case 'u': if strings.HasPrefix("use", fields[0]) { fields[0] = "use" if len(fields) != 4 { return nil, ErrInvalidArgs } switch fields[2][0] { case 'd': if !strings.HasPrefix("domain", fields[2]) { return nil, ErrInvalidArgs } fields[2] = "domain" case 'f': if !strings.HasPrefix("folder", fields[2]) { return nil, ErrInvalidArgs } fields[2] = "folder" case 'p': if !strings.HasPrefix("page", fields[2]) { return nil, ErrInvalidArgs } fields[2] = "page" default: return nil, ErrInvalidArgs } return fields, nil } } return nil, ErrInvalidArgs } func RunCommand(cmd *Command, state *BrowserState) error { switch cmd.Name { case "about": return About(state) case "root": return Root(state, true) case "Root": return Root(state, false) case "reload": return Reload(state) case "back": num := 1 if len(cmd.Args) == 1 { num, _ = strconv.Atoi(cmd.Args[0]) } return Back(state, num) case "forward": num := 1 if len(cmd.Args) == 1 { num, _ = strconv.Atoi(cmd.Args[0]) } return Forward(state, num) case "next": return Next(state) case "previous": return Previous(state) case "up": return Up(state) case "go": return Go(state, cmd.Args[0]) case "help": return Help(state, cmd.Args[0]) case "outline": return Outline(state) case "pipe": return Pipe(state, cmd.Args[0]) case "default": return HandleResource(state) case "print": return Print(state) case "links": return Links(state) case "history": return HistoryCmd(state) case "save": return Save(state, cmd.Args[0]) case "mark": return Mark(state, cmd.Args) case "tour": return TourCmd(state, cmd.Args) case "identity": return IdentityCmd(state, cmd.Args) case "quit": os.Exit(0) } return ErrUnknownCommand }