X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/7fc4ce13ed7ece2a24d110d9e97725eb719b30af..c8a0070a611011c706e67da52a7f17ebfc0c0c1a:/src/redis-cli.c?ds=sidebyside diff --git a/src/redis-cli.c b/src/redis-cli.c index b1914414..2aedcc4a 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -39,11 +39,13 @@ #include #include #include +#include #include "hiredis.h" #include "sds.h" #include "zmalloc.h" #include "linenoise.h" +#include "help.h" #define REDIS_NOTUSED(V) ((void) V) @@ -67,6 +69,7 @@ static struct config { } config; static void usage(); +char *redisGitSHA1(void); /*------------------------------------------------------------------------------ * Utility functions @@ -82,6 +85,149 @@ static long long mstime(void) { return mst; } +/*------------------------------------------------------------------------------ + * Help functions + *--------------------------------------------------------------------------- */ + +#define CLI_HELP_COMMAND 1 +#define CLI_HELP_GROUP 2 + +typedef struct { + int type; + int argc; + sds *argv; + sds full; + + /* Only used for help on commands */ + struct commandHelp *org; +} helpEntry; + +static helpEntry *helpEntries; +static int helpEntriesLen; + +static void cliInitHelp() { + int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); + int groupslen = sizeof(commandGroups)/sizeof(char*); + int i, len, pos = 0; + helpEntry tmp; + + helpEntriesLen = len = commandslen+groupslen; + helpEntries = malloc(sizeof(helpEntry)*len); + + for (i = 0; i < groupslen; i++) { + tmp.argc = 1; + tmp.argv = malloc(sizeof(sds)); + tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); + tmp.full = tmp.argv[0]; + tmp.type = CLI_HELP_GROUP; + tmp.org = NULL; + helpEntries[pos++] = tmp; + } + + for (i = 0; i < commandslen; i++) { + tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); + tmp.full = sdsnew(commandHelp[i].name); + tmp.type = CLI_HELP_COMMAND; + tmp.org = &commandHelp[i]; + helpEntries[pos++] = tmp; + } +} + +/* Output command help to stdout. */ +static void cliOutputCommandHelp(struct commandHelp *help, int group) { + printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); + printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); + printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); + if (group) { + printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); + } +} + +/* Print generic help. */ +static void cliOutputGenericHelp() { + printf( + "redis-cli %s\r\n" + "Type: \"help @\" to get a list of commands in \r\n" + " \"help \" for help on \r\n" + " \"help \" to get a list of possible help topics\r\n" + " \"quit\" to exit\r\n", + REDIS_VERSION + ); +} + +/* Output all command help, filtering by group or command name. */ +static void cliOutputHelp(int argc, char **argv) { + int i, j, len; + int group = -1; + helpEntry *entry; + struct commandHelp *help; + + if (argc == 0) { + cliOutputGenericHelp(); + return; + } else if (argc > 0 && argv[0][0] == '@') { + len = sizeof(commandGroups)/sizeof(char*); + for (i = 0; i < len; i++) { + if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { + group = i; + break; + } + } + } + + assert(argc > 0); + for (i = 0; i < helpEntriesLen; i++) { + entry = &helpEntries[i]; + if (entry->type != CLI_HELP_COMMAND) continue; + + help = entry->org; + if (group == -1) { + /* Compare all arguments */ + if (argc == entry->argc) { + for (j = 0; j < argc; j++) { + if (strcasecmp(argv[j],entry->argv[j]) != 0) break; + } + if (j == argc) { + cliOutputCommandHelp(help,1); + } + } + } else { + if (group == help->group) { + cliOutputCommandHelp(help,0); + } + } + } + printf("\r\n"); +} + +static void completionCallback(const char *buf, linenoiseCompletions *lc) { + size_t startpos = 0; + int mask; + int i; + size_t matchlen; + sds tmp; + + if (strncasecmp(buf,"help ",5) == 0) { + startpos = 5; + while (isspace(buf[startpos])) startpos++; + mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; + } else { + mask = CLI_HELP_COMMAND; + } + + for (i = 0; i < helpEntriesLen; i++) { + if (!(helpEntries[i].type & mask)) continue; + + matchlen = strlen(buf+startpos); + if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { + tmp = sdsnewlen(buf,startpos); + tmp = sdscat(tmp,helpEntries[i].full); + linenoiseAddCompletion(lc,tmp); + sdsfree(tmp); + } + } +} + /*------------------------------------------------------------------------------ * Networking / parsing *--------------------------------------------------------------------------- */ @@ -157,22 +303,18 @@ static sds cliFormatReply(redisReply *r, char *prefix) { sds out = sdsempty(); switch (r->type) { case REDIS_REPLY_ERROR: - out = sdscat(out,prefix); if (config.tty) out = sdscat(out,"(error) "); out = sdscatprintf(out,"%s\n", r->str); break; case REDIS_REPLY_STATUS: - out = sdscat(out,prefix); out = sdscat(out,r->str); out = sdscat(out,"\n"); break; case REDIS_REPLY_INTEGER: - out = sdscat(out,prefix); if (config.tty) out = sdscat(out,"(integer) "); out = sdscatprintf(out,"%lld\n",r->integer); break; case REDIS_REPLY_STRING: - out = sdscat(out,prefix); if (config.raw_output || !config.tty) { out = sdscatlen(out,r->str,r->len); } else { @@ -183,23 +325,44 @@ static sds cliFormatReply(redisReply *r, char *prefix) { } break; case REDIS_REPLY_NIL: - out = sdscat(out,prefix); out = sdscat(out,"(nil)\n"); break; case REDIS_REPLY_ARRAY: if (r->elements == 0) { - out = sdscat(out,prefix); out = sdscat(out,"(empty list or set)\n"); } else { - unsigned int i; + 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++) { - tmp = cliFormatReply(r->element[i],prefix); - out = sdscat(out,prefix); + /* 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: @@ -234,30 +397,19 @@ static int cliReadReply() { 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]; size_t *argvlen; int j; + if (context == NULL) { + printf("Not connected, please use: connect \n"); + return REDIS_OK; + } + config.raw_output = !strcasecmp(command,"info"); - if (!strcasecmp(command,"help")) { - showInteractiveHelp(); + if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { + cliOutputHelp(--argc, ++argv); return REDIS_OK; } if (!strcasecmp(command,"shutdown")) config.shutdown = 1; @@ -274,6 +426,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) { redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); while (config.monitor_mode) { if (cliReadReply() != REDIS_OK) exit(1); + fflush(stdout); } if (config.pubsub_mode) { @@ -300,7 +453,8 @@ static int parseOptions(int argc, char **argv) { int lastarg = i==argc-1; if (!strcmp(argv[i],"-h") && !lastarg) { - config.hostip = argv[i+1]; + sdsfree(config.hostip); + config.hostip = sdsnew(argv[i+1]); i++; } else if (!strcmp(argv[i],"-h") && lastarg) { usage(); @@ -333,7 +487,7 @@ static int parseOptions(int argc, char **argv) { "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; @@ -387,7 +541,9 @@ static void repl() { sds *argv; config.interactive = 1; - while((line = linenoise("redis> ")) != NULL) { + linenoiseSetCompletionCallback(completionCallback); + + while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) { if (line[0] != '\0') { argv = sdssplitargs(line,&argc); linenoiseHistoryAdd(line); @@ -400,14 +556,18 @@ static void repl() { strcasecmp(argv[0],"exit") == 0) { exit(0); + } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { + sdsfree(config.hostip); + config.hostip = sdsnew(argv[1]); + config.hostport = atoi(argv[2]); + cliConnect(1); + } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { + linenoiseClearScreen(); } else { 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"); + cliConnect(1); /* If we still cannot send the command, * print error and abort. */ @@ -415,8 +575,9 @@ static void repl() { cliPrintContextErrorAndExit(); } elapsed = mstime()-start_time; - if (elapsed > 500) printf("%.2f seconds\n", - (double)elapsed/1000); + if (elapsed >= 500) { + printf("(%.2fs)\n",(double)elapsed/1000); + } } } /* Free the argument vector */ @@ -446,7 +607,7 @@ static int noninteractive(int argc, char **argv) { int main(int argc, char **argv) { int firstarg; - config.hostip = "127.0.0.1"; + config.hostip = sdsnew("127.0.0.1"); config.hostport = 6379; config.hostsocket = NULL; config.repeat = 1; @@ -461,6 +622,7 @@ int main(int argc, char **argv) { config.historyfile = NULL; config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL); config.mb_sep = '\n'; + cliInitHelp(); if (getenv("HOME") != NULL) { config.historyfile = malloc(256);