#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>
+#include <assert.h>
#include "hiredis.h"
#include "sds.h"
#include "zmalloc.h"
#include "linenoise.h"
+#include "help.h"
#define REDIS_NOTUSED(V) ((void) V)
} config;
static void usage();
+char *redisGitSHA1(void);
/*------------------------------------------------------------------------------
* Utility functions
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 @<group>\" to get a list of commands in <group>\r\n"
+ " \"help <command>\" for help on <command>\r\n"
+ " \"help <tab>\" 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
*--------------------------------------------------------------------------- */
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 {
}
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:
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 <host> <port>\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;
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
while (config.monitor_mode) {
if (cliReadReply() != REDIS_OK) exit(1);
+ fflush(stdout);
}
if (config.pubsub_mode) {
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();
"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;
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);
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. */
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 */
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;
config.historyfile = NULL;
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
config.mb_sep = '\n';
+ cliInitHelp();
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);