| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- package main
- import (
- "flag"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "github.com/holys/goredis"
- "github.com/peterh/liner"
- )
- var (
- hostname = flag.String("h", getEnv("REDIS_HOST", "127.0.0.1"), "Server hostname")
- port = flag.String("p", getEnv("REDIS_PORT", "6379"), "Server server port")
- socket = flag.String("s", "", "Server socket. (overwrites hostname and port)")
- dbn = flag.Int("n", 0, "Database number(default 0)")
- auth = flag.String("a", "", "Password to use when connecting to the server")
- outputRaw = flag.Bool("raw", false, "Use raw formatting for replies")
- showWelcome = flag.Bool("welcome", false, "show welcome message, mainly for web usage via gotty")
- )
- var (
- line *liner.State
- historyPath = filepath.Join(os.Getenv("HOME"), ".gorediscli_history") // $HOME/.gorediscli_history
- mode int
- client *goredis.Client
- )
- // output
- const (
- stdMode = iota
- rawMode
- )
- func main() {
- flag.Parse()
- if *outputRaw {
- mode = rawMode
- } else {
- mode = stdMode
- }
- // Start interactive mode when no command is provided
- if flag.NArg() == 0 {
- repl()
- }
- noninteractive(flag.Args())
- }
- func getEnv(key string, defaultValue string) string {
- value, found := os.LookupEnv(key)
- if !found {
- return defaultValue
- }
- return value
- }
- // Read-Eval-Print Loop
- func repl() {
- line = liner.NewLiner()
- defer line.Close()
- line.SetCtrlCAborts(true)
- setCompletionHandler()
- loadHistory()
- defer saveHistory()
- reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`)
- prompt := ""
- cliConnect()
- if *showWelcome {
- showWelcomeMsg()
- }
- for {
- addr := addr()
- if *dbn > 0 && *dbn < 16 {
- prompt = fmt.Sprintf("%s[%d]> ", addr, *dbn)
- } else {
- prompt = fmt.Sprintf("%s> ", addr)
- }
- cmd, err := line.Prompt(prompt)
- if err != nil {
- fmt.Printf("%s\n", err.Error())
- return
- }
- cmds := reg.FindAllString(cmd, -1)
- if len(cmds) == 0 {
- continue
- } else {
- appendHistory(cmds)
- cmd := strings.ToLower(cmds[0])
- if cmd == "help" || cmd == "?" {
- printHelp(cmds)
- } else if cmd == "quit" || cmd == "exit" {
- os.Exit(0)
- } else if cmd == "clear" {
- println("Please use Ctrl + L instead")
- } else if cmd == "connect" {
- reconnect(cmds[1:])
- } else if cmd == "mode" {
- switchMode(cmds[1:])
- } else {
- cliSendCommand(cmds)
- }
- }
- }
- }
- func appendHistory(cmds []string) {
- // make a copy of cmds
- cloneCmds := make([]string, len(cmds))
- for i, cmd := range cmds {
- cloneCmds[i] = cmd
- }
- // for security reason, hide the password with ******
- if len(cloneCmds) == 2 && strings.ToLower(cloneCmds[0]) == "auth" {
- cloneCmds[1] = "******"
- }
- if len(cloneCmds) == 4 && strings.ToLower(cloneCmds[0]) == "connect" {
- cloneCmds[3] = "******"
- }
- line.AppendHistory(strings.Join(cloneCmds, " "))
- }
- func cliSendCommand(cmds []string) {
- cliConnect()
- if len(cmds) == 0 {
- return
- }
- args := make([]interface{}, len(cmds[1:]))
- for i := range args {
- args[i] = strings.Trim(string(cmds[1+i]), "\"'")
- }
- cmd := strings.ToLower(cmds[0])
- if cmd == "monitor" {
- respChan := make(chan interface{})
- stopChan := make(chan struct{})
- err := client.Monitor(respChan, stopChan)
- if err != nil {
- fmt.Printf("(error) %s\n", err.Error())
- return
- }
- for {
- select {
- case mr := <-respChan:
- printReply(0, mr, mode)
- fmt.Printf("\n")
- case <-stopChan:
- fmt.Println("Error: Server closed the connection")
- return
- }
- }
- }
- r, err := client.Do(cmd, args...)
- if err == nil && strings.ToLower(cmd) == "select" {
- *dbn, _ = strconv.Atoi(cmds[1])
- }
- if err != nil {
- fmt.Printf("(error) %s", err.Error())
- } else {
- if cmd == "info" {
- printInfo(r)
- } else {
- printReply(0, r, mode)
- }
- }
- fmt.Printf("\n")
- }
- func cliConnect() {
- if client == nil {
- addr := addr()
- client = goredis.NewClient(addr, "")
- client.SetMaxIdleConns(1)
- sendPing(client)
- sendSelect(client, *dbn)
- sendAuth(client, *auth)
- }
- }
- func reconnect(args []string) {
- if len(args) < 2 {
- fmt.Println("(error) invalid connect arguments. At least provides host and port.")
- return
- }
- h := args[0]
- p := args[1]
- var auth string
- if len(args) > 2 {
- auth = args[2]
- }
- if h != "" && p != "" {
- addr := fmt.Sprintf("%s:%s", h, p)
- client = goredis.NewClient(addr, "")
- }
- if err := sendPing(client); err != nil {
- return
- }
- // change prompt
- hostname = &h
- port = &p
- if auth != "" {
- err := sendAuth(client, auth)
- if err != nil {
- return
- }
- }
- fmt.Printf("connected %s:%s successfully \n", h, p)
- }
- func switchMode(args []string) {
- if len(args) != 1 {
- fmt.Println("invalid args. Should be MODE [raw|std]")
- return
- }
- m := strings.ToLower(args[0])
- if m != "raw" && m != "std" {
- fmt.Println("invalid args. Should be MODE [raw|std]")
- return
- }
- switch m {
- case "std":
- mode = stdMode
- case "raw":
- mode = rawMode
- }
- return
- }
- func addr() string {
- var addr string
- if len(*socket) > 0 {
- addr = *socket
- } else {
- addr = fmt.Sprintf("%s:%s", *hostname, *port)
- }
- return addr
- }
- func noninteractive(args []string) {
- cliSendCommand(args)
- }
- func printInfo(reply interface{}) {
- switch reply := reply.(type) {
- case []byte:
- fmt.Printf("%s", reply)
- //some redis proxies don't support this command.
- case goredis.Error:
- fmt.Printf("(error) %s", string(reply))
- }
- }
- func printReply(level int, reply interface{}, mode int) {
- switch mode {
- case stdMode:
- printStdReply(level, reply)
- case rawMode:
- printRawReply(level, reply)
- default:
- printStdReply(level, reply)
- }
- }
- func printStdReply(level int, reply interface{}) {
- switch reply := reply.(type) {
- case int64:
- fmt.Printf("(integer) %d", reply)
- case string:
- fmt.Printf("%s", reply)
- case []byte:
- fmt.Printf("%q", reply)
- case nil:
- fmt.Printf("(nil)")
- case goredis.Error:
- fmt.Printf("(error) %s", string(reply))
- case []interface{}:
- for i, v := range reply {
- if i != 0 {
- fmt.Printf("%s", strings.Repeat(" ", level*4))
- }
- s := fmt.Sprintf("%d) ", i+1)
- fmt.Printf("%-4s", s)
- printStdReply(level+1, v)
- if i != len(reply)-1 {
- fmt.Printf("\n")
- }
- }
- default:
- fmt.Printf("Unknown reply type: %+v", reply)
- }
- }
- func printRawReply(level int, reply interface{}) {
- switch reply := reply.(type) {
- case int64:
- fmt.Printf("%d", reply)
- case string:
- fmt.Printf("%s", reply)
- case []byte:
- fmt.Printf("%s", reply)
- case nil:
- // do nothing
- case goredis.Error:
- fmt.Printf("%s\n", string(reply))
- case []interface{}:
- for i, v := range reply {
- if i != 0 {
- fmt.Printf("%s", strings.Repeat(" ", level*4))
- }
- printRawReply(level+1, v)
- if i != len(reply)-1 {
- fmt.Printf("\n")
- }
- }
- default:
- fmt.Printf("Unknown reply type: %+v", reply)
- }
- }
- func printGenericHelp() {
- msg :=
- `redis-cli
- Type: "help <command>" for help on <command>
- `
- fmt.Println(msg)
- }
- func printCommandHelp(arr []string) {
- fmt.Println()
- fmt.Printf("\t%s %s \n", arr[0], arr[1])
- fmt.Printf("\tGroup: %s \n", arr[2])
- fmt.Println()
- }
- func printHelp(cmds []string) {
- args := cmds[1:]
- if len(args) == 0 {
- printGenericHelp()
- } else if len(args) > 1 {
- fmt.Println()
- } else {
- cmd := strings.ToUpper(args[0])
- for i := 0; i < len(helpCommands); i++ {
- if helpCommands[i][0] == cmd {
- printCommandHelp(helpCommands[i])
- }
- }
- }
- }
- func sendSelect(client *goredis.Client, index int) {
- if index == 0 {
- // do nothing
- return
- }
- if index > 16 || index < 0 {
- index = 0
- fmt.Println("index out of range, should less than 16")
- }
- _, err := client.Do("SELECT", index)
- if err != nil {
- fmt.Printf("%s\n", err.Error())
- }
- }
- func sendAuth(client *goredis.Client, passwd string) error {
- if passwd == "" {
- // do nothing
- return nil
- }
- resp, err := client.Do("AUTH", passwd)
- if err != nil {
- fmt.Printf("(error) %s\n", err.Error())
- return err
- }
- switch resp := resp.(type) {
- case goredis.Error:
- fmt.Printf("(error) %s\n", resp.Error())
- return resp
- }
- return nil
- }
- func sendPing(client *goredis.Client) error {
- _, err := client.Do("PING")
- if err != nil {
- fmt.Printf("%s\n", err.Error())
- return err
- }
- return nil
- }
- func setCompletionHandler() {
- line.SetCompleter(func(line string) (c []string) {
- for _, i := range helpCommands {
- if strings.HasPrefix(i[0], strings.ToUpper(line)) {
- c = append(c, i[0])
- }
- }
- return
- })
- }
- func loadHistory() {
- if f, err := os.Open(historyPath); err == nil {
- line.ReadHistory(f)
- f.Close()
- }
- }
- func saveHistory() {
- if f, err := os.Create(historyPath); err != nil {
- fmt.Printf("Error writing history file: %s", err.Error())
- } else {
- line.WriteHistory(f)
- f.Close()
- }
- }
- func showWelcomeMsg() {
- welcome := `
- Welcome to redis-cli online.
- You can switch to different redis instance with the CONNECT command.
- Usage: CONNECT host port [auth]
- Switch output mode with MODE command.
- Usage: MODE [std | raw]
- `
- fmt.Println(welcome)
- }
|