]> git.saurik.com Git - redis.git/commitdiff
Merge remote branch 'visionmedia/cli-help' into cli-help
authorPieter Noordhuis <pcnoordhuis@gmail.com>
Fri, 26 Nov 2010 19:46:42 +0000 (20:46 +0100)
committerPieter Noordhuis <pcnoordhuis@gmail.com>
Fri, 26 Nov 2010 19:46:42 +0000 (20:46 +0100)
1  2 
Makefile
src/redis-cli.c

diff --combined Makefile
index 185aab8d2038f404c6f5f8143859ca2e03bed2a2,d4d2c149a522331beff539ede619488367a0211d..b72faa1b18c88b8a491deca9d9ebe5e3c1a95f64
+++ b/Makefile
@@@ -8,12 -8,10 +8,15 @@@ all
  install: dummy
        cd src && $(MAKE) $@
  
 -$(TARGETS) clean:
 +clean:
 +      cd src && $(MAKE) $@
 +      cd deps/hiredis && $(MAKE) $@
 +      cd deps/linenoise && $(MAKE) $@
 +
 +$(TARGETS):
        cd src && $(MAKE) $@
  
+ src/help.h:
+       @./utils/generate-command-help.rb > $@
  dummy:
diff --combined src/redis-cli.c
index eef5ad1e07f7776618842d98af0a6a96944cb819,6ae77545e6903635e90f0b753afffb778a0180d6..5f01a936e0d5851fbdd2a64602f0ede0d5789cd6
  #include <ctype.h>
  #include <errno.h>
  #include <sys/stat.h>
 +#include <sys/time.h>
  
 -#include "anet.h"
 +#include "hiredis.h"
  #include "sds.h"
 -#include "adlist.h"
  #include "zmalloc.h"
  #include "linenoise.h"
+ #include "help.h"
  
  #define REDIS_NOTUSED(V) ((void) V)
  
 +static redisContext *context;
  static struct config {
      char *hostip;
      int hostport;
 +    char *hostsocket;
      long repeat;
      int dbnum;
      int interactive;
      char *historyfile;
  } config;
  
 -static int cliReadReply(int fd);
  static void usage();
 +char *redisGitSHA1(void);
  
 -/* Connect to the client. If force is not zero the connection is performed
 - * even if there is already a connected socket. */
 -static int cliConnect(int force) {
 -    char err[ANET_ERR_LEN];
 -    static int fd = ANET_ERR;
 -
 -    if (fd == ANET_ERR || force) {
 -        if (force) close(fd);
 -        fd = anetTcpConnect(err,config.hostip,config.hostport);
 -        if (fd == ANET_ERR) {
 -            fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
 -            return -1;
 -        }
 -        anetTcpNoDelay(NULL,fd);
 -    }
 -    return fd;
 -}
 +/*------------------------------------------------------------------------------
 + * Utility functions
 + *--------------------------------------------------------------------------- */
  
 -static sds cliReadLine(int fd) {
 -    sds line = sdsempty();
 +static long long mstime(void) {
 +    struct timeval tv;
 +    long long mst;
  
 -    while(1) {
 -        char c;
 -        ssize_t ret;
 -
 -        ret = read(fd,&c,1);
 -        if (ret <= 0) {
 -            sdsfree(line);
 -            return NULL;
 -        } else if ((ret == 0) || (c == '\n')) {
 -            break;
 -        } else {
 -            line = sdscatlen(line,&c,1);
 -        }
 -    }
 -    return sdstrim(line,"\r\n");
 +    gettimeofday(&tv, NULL);
 +    mst = ((long)tv.tv_sec)*1000;
 +    mst += tv.tv_usec/1000;
 +    return mst;
  }
  
 -static int cliReadSingleLineReply(int fd, int quiet) {
 -    sds reply = cliReadLine(fd);
 +/*------------------------------------------------------------------------------
 + * Networking / parsing
 + *--------------------------------------------------------------------------- */
  
 -    if (reply == NULL) return 1;
 -    if (!quiet)
 -        printf("%s", reply);
 -    sdsfree(reply);
 -    return 0;
 -}
 +/* Send AUTH command to the server */
 +static int cliAuth() {
 +    redisReply *reply;
 +    if (config.auth == NULL) return REDIS_OK;
  
 -static void printStringRepr(char *s, int len) {
 -    printf("\"");
 -    while(len--) {
 -        switch(*s) {
 -        case '\\':
 -        case '"':
 -            printf("\\%c",*s);
 -            break;
 -        case '\n': printf("\\n"); break;
 -        case '\r': printf("\\r"); break;
 -        case '\t': printf("\\t"); break;
 -        case '\a': printf("\\a"); break;
 -        case '\b': printf("\\b"); break;
 -        default:
 -            if (isprint(*s))
 -                printf("%c",*s);
 -            else
 -                printf("\\x%02x",(unsigned char)*s);
 -            break;
 -        }
 -        s++;
 +    reply = redisCommand(context,"AUTH %s",config.auth);
 +    if (reply != NULL) {
 +        freeReplyObject(reply);
 +        return REDIS_OK;
      }
 -    printf("\"");
 +    return REDIS_ERR;
  }
  
 -static int cliReadBulkReply(int fd) {
 -    sds replylen = cliReadLine(fd);
 -    char *reply, crlf[2];
 -    int bulklen;
 -
 -    if (replylen == NULL) return 1;
 -    bulklen = atoi(replylen);
 -    if (bulklen == -1) {
 -        sdsfree(replylen);
 -        printf("(nil)\n");
 -        return 0;
 -    }
 -    reply = zmalloc(bulklen);
 -    anetRead(fd,reply,bulklen);
 -    anetRead(fd,crlf,2);
 -    if (config.raw_output || !config.tty) {
 -        if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
 -            zfree(reply);
 -            return 1;
 -        }
 -    } else {
 -        /* If you are producing output for the standard output we want
 -         * a more interesting output with quoted characters and so forth */
 -        printStringRepr(reply,bulklen);
 +/* Send SELECT dbnum to the server */
 +static int cliSelect() {
 +    redisReply *reply;
 +    char dbnum[16];
 +    if (config.dbnum == 0) return REDIS_OK;
 +
 +    snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
 +    reply = redisCommand(context,"SELECT %s",dbnum);
 +    if (reply != NULL) {
 +        freeReplyObject(reply);
 +        return REDIS_OK;
      }
 -    zfree(reply);
 -    return 0;
 +    return REDIS_ERR;
  }
  
 -static int cliReadMultiBulkReply(int fd) {
 -    sds replylen = cliReadLine(fd);
 -    int elements, c = 1;
 -    int retval = 0;
 +/* Connect to the client. If force is not zero the connection is performed
 + * even if there is already a connected socket. */
 +static int cliConnect(int force) {
 +    if (context == NULL || force) {
 +        if (context != NULL)
 +            redisFree(context);
  
 -    if (replylen == NULL) return 1;
 -    elements = atoi(replylen);
 -    if (elements == -1) {
 -        sdsfree(replylen);
 -        printf("(nil)\n");
 -        return 0;
 -    }
 -    if (elements == 0) {
 -        printf("(empty list or set)\n");
 -    }
 -    while(elements--) {
 -        if (config.tty) printf("%d. ", c);
 -        if (cliReadReply(fd)) retval = 1;
 -        if (elements) printf("%c",config.mb_sep);
 -        c++;
 +        if (config.hostsocket == NULL) {
 +            context = redisConnect(config.hostip,config.hostport);
 +        } else {
 +            context = redisConnectUnix(config.hostsocket);
 +        }
 +
 +        if (context->err) {
 +            fprintf(stderr,"Could not connect to Redis at ");
 +            if (config.hostsocket == NULL)
 +                fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
 +            else
 +                fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
 +            redisFree(context);
 +            context = NULL;
 +            return REDIS_ERR;
 +        }
 +
 +        /* Do AUTH and select the right DB. */
 +        if (cliAuth() != REDIS_OK)
 +            return REDIS_ERR;
 +        if (cliSelect() != REDIS_OK)
 +            return REDIS_ERR;
      }
 -    return retval;
 +    return REDIS_OK;
  }
  
 -static int cliReadReply(int fd) {
 -    char type;
 -    int nread;
 +static void cliPrintContextErrorAndExit() {
 +    if (context == NULL) return;
 +    fprintf(stderr,"Error: %s\n",context->errstr);
 +    exit(1);
 +}
  
 -    if ((nread = anetRead(fd,&type,1)) <= 0) {
 -        if (config.shutdown) return 0;
 -        if (config.interactive &&
 -            (nread == 0 || (nread == -1 && errno == ECONNRESET)))
 -        {
 -            return ECONNRESET;
 +static sds cliFormatReply(redisReply *r, char *prefix) {
 +    sds out = sdsempty();
 +    switch (r->type) {
 +    case REDIS_REPLY_ERROR:
 +        if (config.tty) out = sdscat(out,"(error) ");
 +        out = sdscatprintf(out,"%s\n", r->str);
 +    break;
 +    case REDIS_REPLY_STATUS:
 +        out = sdscat(out,r->str);
 +        out = sdscat(out,"\n");
 +    break;
 +    case REDIS_REPLY_INTEGER:
 +        if (config.tty) out = sdscat(out,"(integer) ");
 +        out = sdscatprintf(out,"%lld\n",r->integer);
 +    break;
 +    case REDIS_REPLY_STRING:
 +        if (config.raw_output || !config.tty) {
 +            out = sdscatlen(out,r->str,r->len);
          } else {
 -            printf("I/O error while reading from socket: %s",strerror(errno));
 -            exit(1);
 +            /* If you are producing output for the standard output we want
 +             * a more interesting output with quoted characters and so forth */
 +            out = sdscatrepr(out,r->str,r->len);
 +            out = sdscat(out,"\n");
          }
 -    }
 -    switch(type) {
 -    case '-':
 -        if (config.tty) printf("(error) ");
 -        cliReadSingleLineReply(fd,0);
 -        return 1;
 -    case '+':
 -        return cliReadSingleLineReply(fd,0);
 -    case ':':
 -        if (config.tty) printf("(integer) ");
 -        return cliReadSingleLineReply(fd,0);
 -    case '$':
 -        return cliReadBulkReply(fd);
 -    case '*':
 -        return cliReadMultiBulkReply(fd);
 +    break;
 +    case REDIS_REPLY_NIL:
 +        out = sdscat(out,"(nil)\n");
 +    break;
 +    case REDIS_REPLY_ARRAY:
 +        if (r->elements == 0) {
 +            out = sdscat(out,"(empty list or set)\n");
 +        } else {
 +            unsigned int i, idxlen = 0;
 +            char _prefixlen[16];
 +            char _prefixfmt[16];
 +            sds _prefix;
 +            sds tmp;
 +
 +            /* Calculate chars needed to represent the largest index */
 +            i = r->elements;
 +            do {
 +                idxlen++;
 +                i /= 10;
 +            } while(i);
 +
 +            /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
 +            memset(_prefixlen,' ',idxlen+2);
 +            _prefixlen[idxlen+2] = '\0';
 +            _prefix = sdscat(sdsnew(prefix),_prefixlen);
 +
 +            /* Setup prefix format for every entry */
 +            snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
 +
 +            for (i = 0; i < r->elements; i++) {
 +                /* Don't use the prefix for the first element, as the parent
 +                 * caller already prepended the index number. */
 +                out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
 +
 +                /* Format the multi bulk entry */
 +                tmp = cliFormatReply(r->element[i],_prefix);
 +                out = sdscatlen(out,tmp,sdslen(tmp));
 +                sdsfree(tmp);
 +            }
 +            sdsfree(_prefix);
 +        }
 +    break;
      default:
 -        printf("protocol error, got '%c' as reply type byte", type);
 -        return 1;
 +        fprintf(stderr,"Unknown reply type: %d\n", r->type);
 +        exit(1);
      }
 +    return out;
  }
  
 -static int selectDb(int fd) {
 -    int retval;
 -    sds cmd;
 -    char type;
 -
 -    if (config.dbnum == 0)
 -        return 0;
 -
 -    cmd = sdsempty();
 -    cmd = sdscatprintf(cmd,"SELECT %d\r\n",config.dbnum);
 -    anetWrite(fd,cmd,sdslen(cmd));
 -    anetRead(fd,&type,1);
 -    if (type <= 0 || type != '+') return 1;
 -    retval = cliReadSingleLineReply(fd,1);
 -    if (retval) {
 -        return retval;
 +static int cliReadReply() {
 +    redisReply *reply;
 +    sds out;
 +
 +    if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
 +        if (config.shutdown)
 +            return REDIS_OK;
 +        if (config.interactive) {
 +            /* Filter cases where we should reconnect */
 +            if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
 +                return REDIS_ERR;
 +            if (context->err == REDIS_ERR_EOF)
 +                return REDIS_ERR;
 +        }
 +        cliPrintContextErrorAndExit();
 +        return REDIS_ERR; /* avoid compiler warning */
      }
 -    return 0;
 +
 +    out = cliFormatReply(reply,"");
 +    freeReplyObject(reply);
 +    fwrite(out,sdslen(out),1,stdout);
 +    sdsfree(out);
 +    return REDIS_OK;
  }
  
- static void showInteractiveHelp(void) {
-     printf(
-     "\n"
-     "Welcome to redis-cli " REDIS_VERSION "!\n"
-     "Just type any valid Redis command to see a pretty printed output.\n"
-     "\n"
-     "It is possible to quote strings, like in:\n"
-     "  set \"my key\" \"some string \\xff\\n\"\n"
-     "\n"
-     "You can find a list of valid Redis commands at\n"
-     "  http://code.google.com/p/redis/wiki/CommandReference\n"
-     "\n"
-     "Note: redis-cli supports line editing, use up/down arrows for history."
-     "\n\n");
- }
  static int cliSendCommand(int argc, char **argv, int repeat) {
      char *command = argv[0];
 -    int fd, j, retval = 0;
 -    sds cmd;
 +    size_t *argvlen;
 +    int j;
  
      config.raw_output = !strcasecmp(command,"info");
      if (!strcasecmp(command,"help")) {
-         showInteractiveHelp();
+         output_help(--argc, ++argv);
 -        return 0;
 +        return REDIS_OK;
      }
      if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
      if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
      if (!strcasecmp(command,"subscribe") ||
          !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
 -    if ((fd = cliConnect(0)) == -1) return 1;
 -
 -    /* Select db number */
 -    retval = selectDb(fd);
 -    if (retval) {
 -        fprintf(stderr,"Error setting DB num\n");
 -        return 1;
 -    }
  
 -    /* Build the command to send */
 -    cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
 -    for (j = 0; j < argc; j++) {
 -        cmd = sdscatprintf(cmd,"$%lu\r\n",
 -            (unsigned long)sdslen(argv[j]));
 -        cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
 -        cmd = sdscatlen(cmd,"\r\n",2);
 -    }
 +    /* Setup argument length */
 +    argvlen = malloc(argc*sizeof(size_t));
 +    for (j = 0; j < argc; j++)
 +        argvlen[j] = sdslen(argv[j]);
  
      while(repeat--) {
 -        anetWrite(fd,cmd,sdslen(cmd));
 +        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
          while (config.monitor_mode) {
 -            if (cliReadSingleLineReply(fd,0)) exit(1);
 -            printf("\n");
 +            if (cliReadReply() != REDIS_OK) exit(1);
 +            fflush(stdout);
          }
  
          if (config.pubsub_mode) {
 -            printf("Reading messages... (press Ctrl-c to quit)\n");
 +            printf("Reading messages... (press Ctrl-C to quit)\n");
              while (1) {
 -                cliReadReply(fd);
 -                printf("\n\n");
 +                if (cliReadReply() != REDIS_OK) exit(1);
              }
          }
  
 -        retval = cliReadReply(fd);
 -        if (!config.raw_output && config.tty) printf("\n");
 -        if (retval) return retval;
 +        if (cliReadReply() != REDIS_OK)
 +            return REDIS_ERR;
      }
 -    return 0;
 +    return REDIS_OK;
  }
  
 +/*------------------------------------------------------------------------------
 + * User interface
 + *--------------------------------------------------------------------------- */
 +
  static int parseOptions(int argc, char **argv) {
      int i;
  
          int lastarg = i==argc-1;
  
          if (!strcmp(argv[i],"-h") && !lastarg) {
 -            char *ip = zmalloc(32);
 -            if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
 -                printf("Can't resolve %s\n", argv[i]);
 -                exit(1);
 -            }
 -            config.hostip = ip;
 +            config.hostip = argv[i+1];
              i++;
          } else if (!strcmp(argv[i],"-h") && lastarg) {
              usage();
          } else if (!strcmp(argv[i],"-p") && !lastarg) {
              config.hostport = atoi(argv[i+1]);
              i++;
 +        } else if (!strcmp(argv[i],"-s") && !lastarg) {
 +            config.hostsocket = argv[i+1];
 +            i++;
          } else if (!strcmp(argv[i],"-r") && !lastarg) {
              config.repeat = strtoll(argv[i+1],NULL,10);
              i++;
  "automatically used as last argument.\n"
              );
          } else if (!strcmp(argv[i],"-v")) {
 -            printf("redis-cli shipped with Redis version %s\n", REDIS_VERSION);
 +            printf("redis-cli shipped with Redis version %s (%s)\n", REDIS_VERSION, redisGitSHA1());
              exit(0);
          } else {
              break;
@@@ -379,7 -372,7 +364,7 @@@ static sds readArgFromStdin(void) 
  }
  
  static void usage() {
 -    fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
 +    fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
      fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
      fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
      fprintf(stderr, "example: redis-cli get my_passwd\n");
@@@ -420,22 -413,16 +405,22 @@@ static void repl() 
                  {
                      exit(0);
                  } else {
 -                    int err;
 -
 -                    if ((err = cliSendCommand(argc, argv, 1)) != 0) {
 -                        if (err == ECONNRESET) {
 -                            printf("Reconnecting... ");
 -                            fflush(stdout);
 -                            if (cliConnect(1) == -1) exit(1);
 -                            printf("OK\n");
 -                            cliSendCommand(argc,argv,1);
 -                        }
 +                    long long start_time = mstime(), elapsed;
 +
 +                    if (cliSendCommand(argc,argv,1) != REDIS_OK) {
 +                        printf("Reconnecting... ");
 +                        fflush(stdout);
 +                        if (cliConnect(1) != REDIS_OK) exit(1);
 +                        printf("OK\n");
 +
 +                        /* If we still cannot send the command,
 +                         * print error and abort. */
 +                        if (cliSendCommand(argc,argv,1) != REDIS_OK)
 +                            cliPrintContextErrorAndExit();
 +                    }
 +                    elapsed = mstime()-start_time;
 +                    if (elapsed >= 500) {
 +                        printf("(%.2fs)\n",(double)elapsed/1000);
                      }
                  }
              }
@@@ -468,7 -455,6 +453,7 @@@ int main(int argc, char **argv) 
  
      config.hostip = "127.0.0.1";
      config.hostport = 6379;
 +    config.hostsocket = NULL;
      config.repeat = 1;
      config.dbnum = 0;
      config.interactive = 0;
      argc -= firstarg;
      argv += firstarg;
  
 -    if (config.auth != NULL) {
 -        char *authargv[2];
 -        int dbnum = config.dbnum;
 -
 -        /* We need to save the real configured database number and set it to
 -         * zero here, otherwise cliSendCommand() will try to perform the
 -         * SELECT command before the authentication, and it will fail. */
 -        config.dbnum = 0;
 -        authargv[0] = "AUTH";
 -        authargv[1] = config.auth;
 -        cliSendCommand(2, convertToSds(2, authargv), 1);
 -        config.dbnum = dbnum; /* restore the right DB number */
 -    }
 +    /* Try to connect */
 +    if (cliConnect(0) != REDIS_OK) exit(1);
  
      /* Start interactive mode when no command is provided */
      if (argc == 0) repl();