From: Pieter Noordhuis <pcnoordhuis@gmail.com> Date: Mon, 6 Dec 2010 11:31:56 +0000 (+0100) Subject: Merge branch 'master' into brpoplpush X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/bc8ffafeb9bb5b272e118400df55157db41ad897?hp=baa14ef913032baf34645faeae80f5cd6895f97a Merge branch 'master' into brpoplpush --- diff --git a/Makefile b/Makefile index 185aab8d..b72faa1b 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,7 @@ clean: $(TARGETS): cd src && $(MAKE) $@ +src/help.h: + @./utils/generate-command-help.rb > $@ + dummy: diff --git a/deps/linenoise/example.c b/deps/linenoise/example.c index b3f9e9e3..ea0b515c 100644 --- a/deps/linenoise/example.c +++ b/deps/linenoise/example.c @@ -2,9 +2,18 @@ #include <stdlib.h> #include "linenoise.h" + +void completion(const char *buf, linenoiseCompletions *lc) { + if (buf[0] == 'h') { + linenoiseAddCompletion(lc,"hello"); + linenoiseAddCompletion(lc,"hello there"); + } +} + int main(void) { char *line; + linenoiseSetCompletionCallback(completion); linenoiseHistoryLoad("history.txt"); /* Load the history at startup */ while((line = linenoise("hello> ")) != NULL) { if (line[0] != '\0') { diff --git a/deps/linenoise/linenoise.c b/deps/linenoise/linenoise.c index 045862e7..dd434136 100644 --- a/deps/linenoise/linenoise.c +++ b/deps/linenoise/linenoise.c @@ -9,6 +9,8 @@ * the 2010 UNIX computers around. * * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,6 +68,17 @@ * CUF (CUrsor Forward) * Sequence: ESC [ n C * Effect: moves cursor forward of n chars + * + * The following are used to clear the screen: ESC [ H ESC [ 2 J + * This is actually composed of two sequences: + * + * cursorhome + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED2 (Clear entire screen) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen * */ @@ -79,10 +92,12 @@ #include <sys/types.h> #include <sys/ioctl.h> #include <unistd.h> +#include "linenoise.h" #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 static char *unsupported_term[] = {"dumb","cons25",NULL}; +static linenoiseCompletionCallback *completionCallback = 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*/ @@ -195,6 +210,78 @@ static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_ if (write(fd,seq,strlen(seq)) == -1) return; } +static void beep() { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) { + linenoiseCompletions lc = { 0, NULL }; + int nread, nwritten; + char c = 0; + + completionCallback(buf,&lc); + if (lc.len == 0) { + beep(); + } else { + size_t stop = 0, i = 0; + size_t clen; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + clen = strlen(lc.cvec[i]); + refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols); + } else { + refreshLine(fd,prompt,buf,*len,*pos,cols); + } + + nread = read(fd,&c,1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) beep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) { + refreshLine(fd,prompt,buf,*len,*pos,cols); + } + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]); + *len = *pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +void linenoiseClearScreen(void) { + write(STDIN_FILENO,"\x1b[H\x1b[2J",7); +} + static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) { size_t plen = strlen(prompt); size_t pos = 0; @@ -217,6 +304,18 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) nread = read(fd,&c,1); if (nread <= 0) return len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols); + /* Return on errors */ + if (c < 0) return len; + /* Read next character when 0 */ + if (c == 0) continue; + } + switch(c) { case 13: /* enter */ case 4: /* ctrl-d */ @@ -350,6 +449,9 @@ up_down_arrow: pos = len; refreshLine(fd,prompt,buf,len,pos,cols); break; + case 12: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(fd,prompt,buf,len,pos,cols); } } return len; @@ -402,6 +504,19 @@ char *linenoise(const char *prompt) { } } +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) { + size_t len = strlen(str); + char *copy = malloc(len+1); + memcpy(copy,str,len+1); + lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + lc->cvec[lc->len++] = copy; +} + /* Using a circular buffer is smarter, but a bit more complex to handle. */ int linenoiseHistoryAdd(const char *line) { char *linecopy; diff --git a/deps/linenoise/linenoise.h b/deps/linenoise/linenoise.h index 0d76aea9..76a703c2 100644 --- a/deps/linenoise/linenoise.h +++ b/deps/linenoise/linenoise.h @@ -4,6 +4,8 @@ * See linenoise.c for more information. * * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,10 +36,20 @@ #ifndef __LINENOISE_H #define __LINENOISE_H +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, char *); + char *linenoise(const char *prompt); int linenoiseHistoryAdd(const char *line); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(char *filename); int linenoiseHistoryLoad(char *filename); +void linenoiseClearScreen(void); #endif /* __LINENOISE_H */ diff --git a/src/help.h b/src/help.h new file mode 100644 index 00000000..121d9dfa --- /dev/null +++ b/src/help.h @@ -0,0 +1,608 @@ +/* Automatically generated by utils/generate-command-help.rb, do not edit. */ + +#ifndef __REDIS_HELP_H +#define __REDIS_HELP_H + +static char *commandGroups[] = { + "generic", + "string", + "list", + "set", + "sorted_set", + "hash", + "pubsub", + "transactions", + "connection", + "server" +}; + +struct commandHelp { + char *name; + char *params; + char *summary; + int group; + char *since; +} commandHelp[] = { + { "APPEND", + "key value", + "Append a value to a key", + 1, + "1.3.3" }, + { "AUTH", + "password", + "Authenticate to the server", + 8, + "0.08" }, + { "BGREWRITEAOF", + "-", + "Asynchronously rewrite the append-only file", + 9, + "1.07" }, + { "BGSAVE", + "-", + "Asynchronously save the dataset to disk", + 9, + "0.07" }, + { "BLPOP", + "key [key ...] timeout", + "Remove and get the first element in a list, or block until one is available", + 2, + "1.3.1" }, + { "BRPOP", + "key [key ...] timeout", + "Remove and get the last element in a list, or block until one is available", + 2, + "1.3.1" }, + { "CONFIG GET", + "parameter", + "Get the value of a configuration parameter", + 9, + "2.0" }, + { "CONFIG SET", + "parameter value", + "Set a configuration parameter to the given value", + 9, + "2.0" }, + { "DBSIZE", + "-", + "Return the number of keys in the selected database", + 9, + "0.07" }, + { "DEBUG OBJECT", + "key", + "Get debugging information about a key", + 9, + "0.101" }, + { "DEBUG SEGFAULT", + "-", + "Make the server crash", + 9, + "0.101" }, + { "DECR", + "key decrement", + "Decrement the integer value of a key by one", + 1, + "0.07" }, + { "DECRBY", + "key decrement", + "Decrement the integer value of a key by the given number", + 1, + "0.07" }, + { "DEL", + "key [key ...]", + "Delete a key", + 0, + "0.07" }, + { "DISCARD", + "-", + "Discard all commands issued after MULTI", + 7, + "1.3.3" }, + { "ECHO", + "message", + "Echo the given string", + 8, + "0.07" }, + { "EXEC", + "-", + "Execute all commands issued after MULTI", + 7, + "1.1.95" }, + { "EXISTS", + "key", + "Determine if a key exists", + 9, + "0.07" }, + { "EXPIRE", + "key seconds", + "Set a key's time to live in seconds", + 0, + "0.09" }, + { "EXPIREAT", + "key timestamp", + "Set the expiration for a key as a UNIX timestamp", + 0, + "1.1" }, + { "FLUSHALL", + "-", + "Remove all keys from all databases", + 9, + "0.07" }, + { "FLUSHDB", + "-", + "Remove all keys from the current database", + 9, + "0.07" }, + { "GET", + "key", + "Get the value of a key", + 1, + "0.07" }, + { "GETSET", + "key value", + "Set the string value of a key and return its old value", + 1, + "0.091" }, + { "HDEL", + "key field", + "Delete a hash field", + 5, + "1.3.10" }, + { "HEXISTS", + "key field", + "Determine if a hash field exists", + 5, + "1.3.10" }, + { "HGET", + "key field", + "Get the value of a hash field", + 5, + "1.3.10" }, + { "HGETALL", + "key", + "Get all the fields and values in a hash", + 5, + "1.3.10" }, + { "HINCRBY", + "key field increment", + "Increment the integer value of a hash field by the given number", + 5, + "1.3.10" }, + { "HKEYS", + "key", + "Get all the fields in a hash", + 5, + "1.3.10" }, + { "HLEN", + "key", + "Get the number of fields in a hash", + 5, + "1.3.10" }, + { "HMGET", + "key field [field ...]", + "Get the values of all the given hash fields", + 5, + "1.3.10" }, + { "HMSET", + "key field value [field value ...]", + "Set multiple hash fields to multiple values", + 5, + "1.3.8" }, + { "HSET", + "key field value", + "Set the string value of a hash field", + 5, + "1.3.10" }, + { "HSETNX", + "key field value", + "Set the value of a hash field, only if the field does not exist", + 5, + "1.3.8" }, + { "HVALS", + "key", + "Get all the values in a hash", + 5, + "1.3.10" }, + { "INCR", + "key", + "Increment the integer value of a key by one", + 1, + "0.07" }, + { "INCRBY", + "key increment", + "Increment the integer value of a key by the given number", + 1, + "0.07" }, + { "INFO", + "-", + "Get information and statistics about the server", + 9, + "0.07" }, + { "KEYS", + "pattern", + "Find all keys matching the given pattern", + 0, + "0.07" }, + { "LASTSAVE", + "-", + "Get the UNIX time stamp of the last successful save to disk", + 9, + "0.07" }, + { "LINDEX", + "key index", + "Get an element from a list by its index", + 2, + "0.07" }, + { "LINSERT", + "key BEFORE|AFTER pivot value", + "Insert an element before or after another element in a list", + 2, + "2.1.1" }, + { "LLEN", + "key", + "Get the length of a list", + 2, + "0.07" }, + { "LPOP", + "key", + "Remove and get the first element in a list", + 2, + "0.07" }, + { "LPUSH", + "key value", + "Prepend a value to a list", + 2, + "0.07" }, + { "LPUSHX", + "key value", + "Prepend a value to a list, only if the list exists", + 2, + "2.1.1" }, + { "LRANGE", + "key start stop", + "Get a range of elements from a list", + 2, + "0.07" }, + { "LREM", + "key count value", + "Remove elements from a list", + 2, + "0.07" }, + { "LSET", + "key index value", + "Set the value of an element in a list by its index", + 2, + "0.07" }, + { "LTRIM", + "key start stop", + "Trim a list to the specified range", + 2, + "0.07" }, + { "MGET", + "key [key ...]", + "Get the values of all the given keys", + 1, + "0.07" }, + { "MONITOR", + "-", + "Listen for all requests received by the server in real time", + 9, + "0.07" }, + { "MOVE", + "key db", + "Move a key to another database", + 0, + "0.07" }, + { "MSET", + "key value [key value ...]", + "Set multiple keys to multiple values", + 1, + "1.001" }, + { "MSETNX", + "key value [key value ...]", + "Set multiple keys to multiple values, only if none of the keys exist", + 1, + "1.001" }, + { "MULTI", + "-", + "Mark the start of a transaction block", + 7, + "1.1.95" }, + { "PERSIST", + "key", + "Remove the expiration from a key", + 0, + "2.1.2" }, + { "PING", + "-", + "Ping the server", + 8, + "0.07" }, + { "PSUBSCRIBE", + "pattern", + "Listen for messages published to channels matching the given patterns", + 6, + "1.3.8" }, + { "PUBLISH", + "channel message", + "Post a message to a channel", + 6, + "1.3.8" }, + { "PUNSUBSCRIBE", + "[pattern [pattern ...]]", + "Stop listening for messages posted to channels matching the given patterns", + 6, + "1.3.8" }, + { "QUIT", + "-", + "Close the connection", + 8, + "0.07" }, + { "RANDOMKEY", + "-", + "Return a random key from the keyspace", + 0, + "0.07" }, + { "RENAME", + "old new", + "Rename a key", + 0, + "0.07" }, + { "RENAMENX", + "old new", + "Rename a key, only if the new key does not exist", + 0, + "0.07" }, + { "RPOP", + "key", + "Remove and get the last element in a list", + 2, + "0.07" }, + { "RPOPLPUSH", + "source destination", + "Remove the last element in a list, append it to another list and return it", + 2, + "1.1" }, + { "RPUSH", + "key value", + "Append a value to a list", + 2, + "0.07" }, + { "RPUSHX", + "key value", + "Append a value to a list, only if the list exists", + 2, + "2.1.1" }, + { "SADD", + "key member", + "Add a member to a set", + 3, + "0.07" }, + { "SAVE", + "-", + "Synchronously save the dataset to disk", + 9, + "0.07" }, + { "SCARD", + "key", + "Get the number of members in a set", + 3, + "0.07" }, + { "SDIFF", + "key [key ...]", + "Subtract multiple sets", + 3, + "0.100" }, + { "SDIFFSTORE", + "destination key [key ...]", + "Subtract multiple sets and store the resulting set in a key", + 3, + "0.100" }, + { "SELECT", + "index", + "Change the selected database for the current connection", + 8, + "0.07" }, + { "SET", + "key value", + "Set the string value of a key", + 1, + "0.07" }, + { "SETEX", + "key timestamp value", + "Set the value and expiration of a key", + 1, + "1.3.10" }, + { "SETNX", + "key value", + "Set the value of a key, only if the key does not exist", + 1, + "0.07" }, + { "SHUTDOWN", + "-", + "Synchronously save the dataset to disk and then shut down the server", + 9, + "0.07" }, + { "SINTER", + "key [key ...]", + "Intersect multiple sets", + 3, + "0.07" }, + { "SINTERSTORE", + "destination key [key ...]", + "Intersect multiple sets and store the resulting set in a key", + 3, + "0.07" }, + { "SISMEMBER", + "key member", + "Determine if a given value is a member of a set", + 3, + "0.07" }, + { "SLAVEOF", + "host port", + "Make the server a slave of another instance, or promote it as master", + 9, + "0.100" }, + { "SMEMBERS", + "key", + "Get all the members in a set", + 3, + "0.07" }, + { "SMOVE", + "source destination member", + "Move a member from one set to another", + 3, + "0.091" }, + { "SORT", + "key [BY pattern] [LIMIT start count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]", + "Sort the elements in a list, set or sorted set", + 0, + "0.07" }, + { "SPOP", + "key", + "Remove and return a random member from a set", + 3, + "0.101" }, + { "SRANDMEMBER", + "key", + "Get a random member from a set", + 3, + "1.001" }, + { "SREM", + "key member", + "Remove a member from a set", + 3, + "0.07" }, + { "STRLEN", + "key", + "Get the length of the value stored in a key", + 1, + "2.1.2" }, + { "SUBSCRIBE", + "channel", + "Listen for messages published to the given channels", + 6, + "1.3.8" }, + { "SUBSTR", + "key start stop", + "Get a substring of the string stored at a key", + 1, + "1.3.4" }, + { "SUNION", + "key [key ...]", + "Add multiple sets", + 3, + "0.091" }, + { "SUNIONSTORE", + "destination key [key ...]", + "Add multiple sets and store the resulting set in a key", + 3, + "0.091" }, + { "SYNC", + "-", + "Internal command used for replication", + 9, + "0.07" }, + { "TTL", + "key", + "Get the time to live for a key", + 0, + "0.100" }, + { "TYPE", + "key", + "Determine the type stored at key", + 0, + "0.07" }, + { "UNSUBSCRIBE", + "[channel [channel ...]]", + "Stop listening for messages posted to the given channels", + 6, + "1.3.8" }, + { "UNWATCH", + "-", + "Forget about all watched keys", + 7, + "2.1.0" }, + { "WATCH", + "key [key ...]", + "Watch the given keys to determine execution of the MULTI/EXEC block", + 7, + "2.1.0" }, + { "ZADD", + "key score member", + "Add a member to a sorted set, or update its score if it already exists", + 4, + "1.1" }, + { "ZCARD", + "key", + "Get the number of members in a sorted set", + 4, + "1.1" }, + { "ZCOUNT", + "key min max", + "Count the members in a sorted set with scores within the given values", + 4, + "1.3.3" }, + { "ZINCRBY", + "key increment member", + "Increment the score of a member in a sorted set", + 4, + "1.1" }, + { "ZINTERSTORE", + "destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]", + "Intersect multiple sorted sets and store the resulting sorted set in a new key", + 4, + "1.3.10" }, + { "ZRANGE", + "key start stop", + "Return a range of members in a sorted set, by index", + 4, + "1.1" }, + { "ZRANGEBYSCORE", + "key min max", + "Return a range of members in a sorted set, by score", + 4, + "1.050" }, + { "ZRANK", + "key member", + "Determine the index of a member in a sorted set", + 4, + "1.3.4" }, + { "ZREM", + "key member", + "Remove a member from a sorted set", + 4, + "1.1" }, + { "ZREMRANGEBYRANK", + "key start stop", + "Remove all members in a sorted set within the given indexes", + 4, + "1.3.4" }, + { "ZREMRANGEBYSCORE", + "key min max", + "Remove all members in a sorted set within the given scores", + 4, + "1.1" }, + { "ZREVRANGE", + "key start stop", + "Return a range of members in a sorted set, by index, with scores ordered from high to low", + 4, + "1.1" }, + { "ZREVRANK", + "key member", + "Determine the index of a member in a sorted set, with scores ordered from high to low", + 4, + "1.3.4" }, + { "ZSCORE", + "key member", + "Get the score associated with the given member in a sorted set", + 4, + "1.1" }, + { "ZUNIONSTORE", + "destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]", + "Add multiple sorted sets and store the resulting sorted set in a new key", + 4, + "1.3.10" } +}; + +#endif diff --git a/src/redis-cli.c b/src/redis-cli.c index 09ab9189..2aedcc4a 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -39,11 +39,13 @@ #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) @@ -83,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 @<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 *--------------------------------------------------------------------------- */ @@ -252,22 +397,6 @@ 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; @@ -279,8 +408,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) { } 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; @@ -412,6 +541,8 @@ static void repl() { sds *argv; config.interactive = 1; + linenoiseSetCompletionCallback(completionCallback); + while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) { if (line[0] != '\0') { argv = sdssplitargs(line,&argc); @@ -430,6 +561,8 @@ static void repl() { 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; @@ -489,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); diff --git a/utils/generate-command-help.rb b/utils/generate-command-help.rb new file mode 100755 index 00000000..f730eaf1 --- /dev/null +++ b/utils/generate-command-help.rb @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby + +GROUPS = [ + "generic", + "string", + "list", + "set", + "sorted_set", + "hash", + "pubsub", + "transactions", + "connection", + "server" +].freeze + +GROUPS_BY_NAME = Hash[* + GROUPS.each_with_index.map do |n,i| + [n,i] + end.flatten +].freeze + +def argument arg + name = arg["name"].is_a?(Array) ? arg["name"].join(" ") : arg["name"] + name = arg["enum"].join "|" if "enum" == arg["type"] + name = arg["command"] + " " + name if arg["command"] + if arg["multiple"] + name = "#{name} [#{name} ...]" + end + if arg["optional"] + name = "[#{name}]" + end + name +end + +def arguments command + return "-" unless command["arguments"] + command["arguments"].map do |arg| + argument arg + end.join " " +end + +def commands + return @commands if @commands + + require "net/http" + require "net/https" + require "json" + require "uri" + + url = URI.parse "https://github.com/antirez/redis-doc/raw/master/commands.json" + client = Net::HTTP.new url.host, url.port + client.use_ssl = true + response = client.get url.path + if response.is_a?(Net::HTTPSuccess) + @commands = JSON.parse(response.body) + else + response.error! + end +end + +def generate_groups + GROUPS.map do |n| + "\"#{n}\"" + end.join(",\n "); +end + +def generate_commands + commands.to_a.sort do |x,y| + x[0] <=> y[0] + end.map do |key, command| + group = GROUPS_BY_NAME[command["group"]] + if group.nil? + STDERR.puts "Please update groups array in #{__FILE__}" + raise "Unknown group #{command["group"]}" + end + + ret = <<-SPEC +{ "#{key}", + "#{arguments(command)}", + "#{command["summary"]}", + #{group}, + "#{command["since"]}" } + SPEC + ret.strip + end.join(",\n ") +end + +# Write to stdout +puts <<-HELP_H +/* Automatically generated by #{__FILE__}, do not edit. */ + +#ifndef __REDIS_HELP_H +#define __REDIS_HELP_H + +static char *commandGroups[] = { + #{generate_groups} +}; + +struct commandHelp { + char *name; + char *params; + char *summary; + int group; + char *since; +} commandHelp[] = { + #{generate_commands} +}; + +#endif +HELP_H +