summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--deps/linenoise/example.c9
-rw-r--r--deps/linenoise/linenoise.c95
-rw-r--r--deps/linenoise/linenoise.h9
-rw-r--r--src/help.h608
-rw-r--r--src/redis-cli.c168
-rwxr-xr-xutils/generate-command-help.rb111
7 files changed, 985 insertions, 18 deletions
diff --git a/Makefile b/Makefile
index 185aab8d2..b72faa1b1 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 b3f9e9e35..ea0b515c1 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 045862e78..b6f4d4f9b 100644
--- a/deps/linenoise/linenoise.c
+++ b/deps/linenoise/linenoise.c
@@ -79,10 +79,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 +197,74 @@ 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 */
+}
+
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
size_t plen = strlen(prompt);
size_t pos = 0;
@@ -217,6 +287,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 */
@@ -402,6 +484,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 0d76aea9c..c44bc3ade 100644
--- a/deps/linenoise/linenoise.h
+++ b/deps/linenoise/linenoise.h
@@ -34,6 +34,15 @@
#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);
diff --git a/src/help.h b/src/help.h
new file mode 100644
index 000000000..121d9dfaf
--- /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 09ab9189f..a39874722 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)
@@ -84,6 +86,149 @@ static long long mstime(void) {
}
/*------------------------------------------------------------------------------
+ * 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);
@@ -489,6 +620,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 000000000..f730eaf10
--- /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
+