diff options
author | antirez <antirez@gmail.com> | 2010-09-03 11:34:40 +0200 |
---|---|---|
committer | antirez <antirez@gmail.com> | 2010-09-03 11:34:40 +0200 |
commit | eff09a7bd89d198c2fd7454083b71cd6ba08149f (patch) | |
tree | c2b96835a7d1ef9239837089509df95ea1e2cf92 | |
parent | efc8a6beee5f8ed034b2c31df3d72cd6eec6df9e (diff) | |
download | redis-eff09a7bd89d198c2fd7454083b71cd6ba08149f.tar.gz |
redis-cli completely replaced with the version in Redis master
-rw-r--r-- | linenoise.c | 48 | ||||
-rw-r--r-- | linenoise.h | 4 | ||||
-rw-r--r-- | redis-cli.c | 202 | ||||
-rw-r--r-- | sds.c | 89 | ||||
-rw-r--r-- | sds.h | 1 |
5 files changed, 236 insertions, 108 deletions
diff --git a/linenoise.c b/linenoise.c index 0c04d03fb..54f5a27d8 100644 --- a/linenoise.c +++ b/linenoise.c @@ -70,6 +70,7 @@ */ #include "fmacros.h" + #include <termios.h> #include <unistd.h> #include <stdlib.h> @@ -81,13 +82,14 @@ #include <sys/ioctl.h> #include <unistd.h> +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 static char *unsupported_term[] = {"dumb","cons25",NULL}; static struct termios orig_termios; /* in order to restore at exit */ static int rawmode = 0; /* for atexit() function to check if restore is needed*/ static int atexit_registered = 0; /* register atexit just 1 time */ -static int history_max_len = 100; +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; char **history = NULL; @@ -219,11 +221,10 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) if (nread <= 0) return len; switch(c) { case 13: /* enter */ - history_len--; - return len; case 4: /* ctrl-d */ history_len--; - return (len == 0) ? -1 : (int)len; + free(history[history_len]); + return (len == 0 && c == 4) ? -1 : (int)len; case 3: /* ctrl-c */ errno = EAGAIN; return -1; @@ -396,7 +397,7 @@ int linenoiseHistoryAdd(const char *line) { char *linecopy; if (history_max_len == 0) return 0; - if (history == 0) { + if (history == NULL) { history = malloc(sizeof(char*)*history_max_len); if (history == NULL) return 0; memset(history,0,(sizeof(char*)*history_max_len)); @@ -404,6 +405,7 @@ int linenoiseHistoryAdd(const char *line) { linecopy = strdup(line); if (!linecopy) return 0; if (history_len == history_max_len) { + free(history[0]); memmove(history,history+1,sizeof(char*)*(history_max_len-1)); history_len--; } @@ -431,3 +433,39 @@ int linenoiseHistorySetMaxLen(int len) { history_len = history_max_len; return 1; } + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(char *filename) { + FILE *fp = fopen(filename,"w"); + int j; + + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} diff --git a/linenoise.h b/linenoise.h index ff45e2c47..0d76aea9c 100644 --- a/linenoise.h +++ b/linenoise.h @@ -35,7 +35,9 @@ #define __LINENOISE_H char *linenoise(const char *prompt); -int linenoiseHistoryAdd(char *line); +int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(char *filename); +int linenoiseHistoryLoad(char *filename); #endif /* __LINENOISE_H */ diff --git a/redis-cli.c b/redis-cli.c index fcb42ec75..8b7ef98ab 100644 --- a/redis-cli.c +++ b/redis-cli.c @@ -36,6 +36,7 @@ #include <unistd.h> #include <ctype.h> #include <errno.h> +#include <sys/stat.h> #include "anet.h" #include "sds.h" @@ -54,13 +55,15 @@ static struct config { int hostport; long repeat; int dbnum; - int argn_from_stdin; int interactive; int shutdown; int monitor_mode; int pubsub_mode; - int raw_output; + int raw_output; /* output mode per command */ + int tty; /* flag for default output format */ + char mb_sep; char *auth; + char *historyfile; } config; static int cliReadReply(int fd); @@ -92,7 +95,7 @@ static sds cliReadLine(int fd) { ssize_t ret; ret = read(fd,&c,1); - if (ret == -1) { + if (ret <= 0) { sdsfree(line); return NULL; } else if ((ret == 0) || (c == '\n')) { @@ -109,7 +112,7 @@ static int cliReadSingleLineReply(int fd, int quiet) { if (reply == NULL) return 1; if (!quiet) - printf("%s\n", reply); + printf("%s", reply); sdsfree(reply); return 0; } @@ -136,7 +139,7 @@ static void printStringRepr(char *s, int len) { } s++; } - printf("\"\n"); + printf("\""); } static int cliReadBulkReply(int fd) { @@ -154,7 +157,7 @@ static int cliReadBulkReply(int fd) { reply = zmalloc(bulklen); anetRead(fd,reply,bulklen); anetRead(fd,crlf,2); - if (config.raw_output || !isatty(fileno(stdout))) { + if (config.raw_output || !config.tty) { if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) { zfree(reply); return 1; @@ -171,6 +174,7 @@ static int cliReadBulkReply(int fd) { static int cliReadMultiBulkReply(int fd) { sds replylen = cliReadLine(fd); int elements, c = 1; + int retval = 0; if (replylen == NULL) return 1; elements = atoi(replylen); @@ -183,11 +187,12 @@ static int cliReadMultiBulkReply(int fd) { printf("(empty list or set)\n"); } while(elements--) { - printf("%d. ", c); - if (cliReadReply(fd)) return 1; + if (config.tty) printf("%d. ", c); + if (cliReadReply(fd)) retval = 1; + if (elements) printf("%c",config.mb_sep); c++; } - return 0; + return retval; } static int cliReadReply(int fd) { @@ -207,20 +212,20 @@ static int cliReadReply(int fd) { } switch(type) { case '-': - printf("(error) "); + if (config.tty) printf("(error) "); cliReadSingleLineReply(fd,0); return 1; case '+': return cliReadSingleLineReply(fd,0); case ':': - printf("(integer) "); + if (config.tty) printf("(integer) "); return cliReadSingleLineReply(fd,0); case '$': return cliReadBulkReply(fd); case '*': return cliReadMultiBulkReply(fd); default: - printf("protocol error, got '%c' as reply type byte\n", type); + printf("protocol error, got '%c' as reply type byte", type); return 1; } } @@ -245,12 +250,32 @@ static int selectDb(int fd) { return 0; } +static void showInteractiveHelp(void) { + printf( + "\n" + "Welcome to redis-cli 2.0.0!\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; config.raw_output = !strcasecmp(command,"info"); + if (!strcasecmp(command,"help")) { + showInteractiveHelp(); + return 0; + } if (!strcasecmp(command,"shutdown")) config.shutdown = 1; if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; if (!strcasecmp(command,"subscribe") || @@ -276,21 +301,21 @@ static int cliSendCommand(int argc, char **argv, int repeat) { while(repeat--) { anetWrite(fd,cmd,sdslen(cmd)); while (config.monitor_mode) { - cliReadSingleLineReply(fd,0); + if (cliReadSingleLineReply(fd,0)) exit(1); + printf("\n"); } if (config.pubsub_mode) { printf("Reading messages... (press Ctrl-c to quit)\n"); while (1) { cliReadReply(fd); - printf("\n"); + printf("\n\n"); } } retval = cliReadReply(fd); - if (retval) { - return retval; - } + if (!config.raw_output && config.tty) printf("\n"); + if (retval) return retval; } return 0; } @@ -324,9 +349,19 @@ static int parseOptions(int argc, char **argv) { config.auth = argv[i+1]; i++; } else if (!strcmp(argv[i],"-i")) { - config.interactive = 1; + fprintf(stderr, +"Starting interactive mode using -i is deprecated. Interactive mode is started\n" +"by default when redis-cli is executed without a command to execute.\n" + ); } else if (!strcmp(argv[i],"-c")) { - config.argn_from_stdin = 1; + fprintf(stderr, +"Reading last argument from standard input using -c is deprecated.\n" +"When standard input is connected to a pipe or regular file, it is\n" +"automatically used as last argument.\n" + ); + } else if (!strcmp(argv[i],"-v")) { + printf("redis-cli shipped with Redis verison %s\n", "2.0.0"); + exit(0); } else { break; } @@ -352,8 +387,8 @@ static sds readArgFromStdin(void) { } static void usage() { - fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n"); - fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); + 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: echo \"argN\" | redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n"); fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n"); fprintf(stderr, "example: redis-cli get my_passwd\n"); @@ -373,81 +408,22 @@ static char **convertToSds(int count, char** args) { return sds; } -static char **splitArguments(char *line, int *argc) { - char *p = line; - char *current = NULL; - char **vector = NULL; - - *argc = 0; - while(1) { - /* skip blanks */ - while(*p && isspace(*p)) p++; - if (*p) { - /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ - int done = 0; - - if (current == NULL) current = sdsempty(); - while(!done) { - if (inq) { - if (*p == '\\' && *(p+1)) { - char c; - - p++; - switch(*p) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'b': c = '\b'; break; - case 'a': c = '\a'; break; - default: c = *p; break; - } - current = sdscatlen(current,&c,1); - } else if (*p == '"') { - done = 1; - } else { - current = sdscatlen(current,p,1); - } - } else { - switch(*p) { - case ' ': - case '\n': - case '\r': - case '\t': - case '\0': - done=1; - break; - case '"': - inq=1; - break; - default: - current = sdscatlen(current,p,1); - break; - } - } - if (*p) p++; - } - /* add the token to the vector */ - vector = zrealloc(vector,((*argc)+1)*sizeof(char*)); - vector[*argc] = current; - (*argc)++; - current = NULL; - } else { - return vector; - } - } -} - #define LINE_BUFLEN 4096 static void repl() { int argc, j; - char *line, **argv; + char *line; + sds *argv; + config.interactive = 1; while((line = linenoise("redis> ")) != NULL) { if (line[0] != '\0') { - argv = splitArguments(line,&argc); + argv = sdssplitargs(line,&argc); linenoiseHistoryAdd(line); - if (argc > 0) { + if (config.historyfile) linenoiseHistorySave(config.historyfile); + if (argv == NULL) { + printf("Invalid argument(s)\n"); + continue; + } else if (argc > 0) { if (strcasecmp(argv[0],"quit") == 0 || strcasecmp(argv[0],"exit") == 0) { @@ -477,21 +453,43 @@ static void repl() { exit(0); } +static int noninteractive(int argc, char **argv) { + int retval = 0; + struct stat s; + fstat(fileno(stdin), &s); + if (S_ISFIFO(s.st_mode) || S_ISREG(s.st_mode)) { /* pipe, regular file */ + argv = zrealloc(argv, (argc+1)*sizeof(char*)); + argv[argc] = readArgFromStdin(); + retval = cliSendCommand(argc+1, argv, config.repeat); + } else { + /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ + retval = cliSendCommand(argc, argv, config.repeat); + } + return retval; +} + int main(int argc, char **argv) { int firstarg; - char **argvcopy; config.hostip = "127.0.0.1"; config.hostport = 6379; config.repeat = 1; config.dbnum = 0; - config.argn_from_stdin = 0; - config.shutdown = 0; config.interactive = 0; + config.shutdown = 0; config.monitor_mode = 0; config.pubsub_mode = 0; config.raw_output = 0; config.auth = NULL; + config.historyfile = NULL; + config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL); + config.mb_sep = '\n'; + + if (getenv("HOME") != NULL) { + config.historyfile = malloc(256); + snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME")); + linenoiseHistoryLoad(config.historyfile); + } firstarg = parseOptions(argc,argv); argc -= firstarg; @@ -499,20 +497,20 @@ int main(int argc, char **argv) { 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 */ } - if (argc == 0) config.interactive = 1; - if (config.interactive) repl(); - - argvcopy = convertToSds(argc+1, argv); - if (config.argn_from_stdin) { - sds lastarg = readArgFromStdin(); - argvcopy[argc] = lastarg; - argc++; - } - return cliSendCommand(argc, argvcopy, config.repeat); + /* Start interactive mode when no command is provided */ + if (argc == 0) repl(); + /* Otherwise, we have some arguments to execute */ + return noninteractive(argc,convertToSds(argc,argv)); } @@ -357,3 +357,92 @@ sds sdsfromlonglong(long long value) { p++; return sdsnewlen(p,32-(p-buf)); } + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. The caller should sdsfree() all the returned + * strings and finally zfree() the array itself. + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + */ +sds *sdssplitargs(char *line, int *argc) { + char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = zrealloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + zfree(vector); + if (current) sdsfree(current); + return NULL; +} @@ -69,5 +69,6 @@ void sdsfreesplitres(sds *tokens, int count); void sdstolower(sds s); void sdstoupper(sds s); sds sdsfromlonglong(long long value); +sds *sdssplitargs(char *line, int *argc); #endif |