From: Tj Holowaychuk Date: Tue, 16 Nov 2010 13:50:26 +0000 (-0800) Subject: Added redis-cli interactive help support X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/5397f2b596b5189edbed3e45a42d18f3c99341d6 Added redis-cli interactive help support updated via commands.json in redis-doc repo. Currently use `make src/help.h` to re-generate. The following are valid from the REPL: help help [command] help [group] help groups ex: help sort help hash --- diff --git a/Makefile b/Makefile index f6790945..d4d2c149 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,7 @@ install: dummy $(TARGETS) clean: cd src && $(MAKE) $@ +src/help.h: + @./utils/generate-command-help.rb > $@ + dummy: diff --git a/src/help.h b/src/help.h new file mode 100644 index 00000000..365b8b76 --- /dev/null +++ b/src/help.h @@ -0,0 +1,815 @@ + +// Auto-generated, do not edit. + +#include +#include + +/* + * List command groups. + */ + +#define GROUPS \ + G(UNKNOWN, "unknown") \ + G(SET, "set") \ + G(LIST, "list") \ + G(HASH, "hash") \ + G(GENERIC, "generic") \ + G(PUBSUB, "pubsub") \ + G(STRING, "string") \ + G(SERVER, "server") \ + G(CONNECTION, "connection") \ + G(TRANSACTIONS, "transactions") \ + G(SORTED_SET, "sorted_set") + +/* + * Command group types. + */ + +typedef enum { + #define G(GROUP, _) COMMAND_GROUP_##GROUP, + GROUPS + #undef G + COMMAND_GROUP_LENGTH +} command_group_type_t; + +/* + * Command group type names. + */ + +static char *command_group_type_names[] = { + #define G(_, STR) STR, + GROUPS + #undef G +}; + +/* + * Command help struct. + */ + +struct command_help { + char *name; + char *params; + char *summary; + command_group_type_t group; + char *since; +} command_help[] = { + { "APPEND" + , "key value" + , "Append a value to a key" + , COMMAND_GROUP_STRING + , "1.3.3" } + + , { "AUTH" + , "password" + , "Authenticate to the server" + , COMMAND_GROUP_CONNECTION + , "0.08" } + + , { "BGREWRITEAOF" + , "-" + , "Asynchronously rewrite the append-only file" + , COMMAND_GROUP_SERVER + , "1.07" } + + , { "BGSAVE" + , "-" + , "Asynchronously save the dataset to disk" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "BLPOP" + , "(key)+ timeout" + , "Remove and get the first element in a list, or block until one is available" + , COMMAND_GROUP_LIST + , "1.3.1" } + + , { "BRPOP" + , "(key)+ timeout" + , "Remove and get the last element in a list, or block until one is available" + , COMMAND_GROUP_LIST + , "1.3.1" } + + , { "CONFIG GET" + , "parameter" + , "Get the value of a configuration parameter" + , COMMAND_GROUP_SERVER + , "2.0" } + + , { "CONFIG SET" + , "parameter value" + , "Set a configuration parameter to the given value" + , COMMAND_GROUP_SERVER + , "2.0" } + + , { "DBSIZE" + , "-" + , "Return the number of keys in the selected database" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "DEBUG OBJECT" + , "key" + , "Get debugging information about a key" + , COMMAND_GROUP_SERVER + , "0.101" } + + , { "DEBUG SEGFAULT" + , "-" + , "Make the server crash" + , COMMAND_GROUP_SERVER + , "0.101" } + + , { "DECR" + , "key decrement" + , "Decrement the integer value of a key by one" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "DECRBY" + , "key decrement" + , "Decrement the integer value of a key by the given number" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "DEL" + , "(key)+" + , "Delete a key" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "DISCARD" + , "-" + , "Discard all commands issued after MULTI" + , COMMAND_GROUP_TRANSACTIONS + , "1.3.3" } + + , { "ECHO" + , "message" + , "Echo the given string" + , COMMAND_GROUP_CONNECTION + , "0.07" } + + , { "EXEC" + , "-" + , "Execute all commands issued after MULTI" + , COMMAND_GROUP_TRANSACTIONS + , "1.1.95" } + + , { "EXISTS" + , "key" + , "Determine if a key exists" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "EXPIRE" + , "key seconds" + , "Set a key's time to live in seconds" + , COMMAND_GROUP_GENERIC + , "0.09" } + + , { "EXPIREAT" + , "key timestamp" + , "Set the expiration for a key as a UNIX timestamp" + , COMMAND_GROUP_GENERIC + , "1.1" } + + , { "FLUSHALL" + , "-" + , "Remove all keys from all databases" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "FLUSHDB" + , "-" + , "Remove all keys from the current database" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "GET" + , "key" + , "Get the value of a key" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "GETSET" + , "key value" + , "Set the string value of a key and return its old value" + , COMMAND_GROUP_STRING + , "0.091" } + + , { "HDEL" + , "key field" + , "Delete a hash field" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HEXISTS" + , "key field" + , "Determine if a hash field exists" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HGET" + , "key field" + , "Get the value of a hash field" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HGETALL" + , "key" + , "Get all the fields and values in a hash" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HINCRBY" + , "key field increment" + , "Increment the integer value of a hash field by the given number" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HKEYS" + , "key" + , "Get all the fields in a hash" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HLEN" + , "key" + , "Get the number of fields in a hash" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HMGET" + , "key (field)+" + , "Get the values of all the given hash fields" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HMSET" + , "key (field value)+" + , "Set multiple hash fields to multiple values" + , COMMAND_GROUP_HASH + , "1.3.8" } + + , { "HSET" + , "key field value" + , "Set the string value of a hash field" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "HSETNX" + , "key field value" + , "Set the value of a hash field, only if the field does not exist" + , COMMAND_GROUP_HASH + , "1.3.8" } + + , { "HVALS" + , "key" + , "Get all the values in a hash" + , COMMAND_GROUP_HASH + , "1.3.10" } + + , { "INCR" + , "key" + , "Increment the integer value of a key by one" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "INCRBY" + , "key increment" + , "Increment the integer value of a key by the given number" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "INFO" + , "-" + , "Get information and statistics about the server" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "KEYS" + , "pattern" + , "Find all keys matching the given pattern" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "LASTSAVE" + , "-" + , "Get the UNIX time stamp of the last successful save to disk" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "LINDEX" + , "key index" + , "Get an element from a list by its index" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LINSERT" + , "key BEFORE|AFTER pivot value" + , "Insert an element before or after another element in a list" + , COMMAND_GROUP_LIST + , "2.1.1" } + + , { "LLEN" + , "key" + , "Get the length of a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LPOP" + , "key" + , "Remove and get the first element in a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LPUSH" + , "key value" + , "Prepend a value to a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LPUSHX" + , "key value" + , "Prepend a value to a list, only if the list exists" + , COMMAND_GROUP_LIST + , "2.1.1" } + + , { "LRANGE" + , "key start stop" + , "Get a range of elements from a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LREM" + , "key count value" + , "Remove elements from a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LSET" + , "key index value" + , "Set the value of an element in a list by its index" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "LTRIM" + , "key start stop" + , "Trim a list to the specified range" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "MGET" + , "(key)+" + , "Get the values of all the given keys" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "MONITOR" + , "-" + , "Listen for all requests received by the server in real time" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "MOVE" + , "key db" + , "Move a key to another database" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "MSET" + , "(key value)+" + , "Set multiple keys to multiple values" + , COMMAND_GROUP_STRING + , "1.001" } + + , { "MSETNX" + , "(key value)+" + , "Set multiple keys to multiple values, only if none of the keys exist" + , COMMAND_GROUP_STRING + , "1.001" } + + , { "MULTI" + , "-" + , "Mark the start of a transaction block" + , COMMAND_GROUP_TRANSACTIONS + , "1.1.95" } + + , { "PERSIST" + , "key" + , "Remove the expiration from a key" + , COMMAND_GROUP_GENERIC + , "2.1.2" } + + , { "PING" + , "-" + , "Ping the server" + , COMMAND_GROUP_CONNECTION + , "0.07" } + + , { "PSUBSCRIBE" + , "pattern" + , "Listen for messages published to channels matching the given patterns" + , COMMAND_GROUP_PUBSUB + , "1.3.8" } + + , { "PUBLISH" + , "channel message" + , "Post a message to a channel" + , COMMAND_GROUP_PUBSUB + , "1.3.8" } + + , { "PUNSUBSCRIBE" + , "(pattern)*" + , "Stop listening for messages posted to channels matching the given patterns" + , COMMAND_GROUP_PUBSUB + , "1.3.8" } + + , { "QUIT" + , "-" + , "Close the connection" + , COMMAND_GROUP_CONNECTION + , "0.07" } + + , { "RANDOMKEY" + , "-" + , "Return a random key from the keyspace" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "RENAME" + , "old new" + , "Rename a key" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "RENAMENX" + , "old new" + , "Rename a key, only if the new key does not exist" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "RPOP" + , "key" + , "Remove and get the last element in a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "RPOPLPUSH" + , "source destination" + , "Remove the last element in a list, append it to another list and return it" + , COMMAND_GROUP_LIST + , "1.1" } + + , { "RPUSH" + , "key value" + , "Append a value to a list" + , COMMAND_GROUP_LIST + , "0.07" } + + , { "RPUSHX" + , "key value" + , "Append a value to a list, only if the list exists" + , COMMAND_GROUP_LIST + , "2.1.1" } + + , { "SADD" + , "key member" + , "Add a member to a set" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SAVE" + , "-" + , "Synchronously save the dataset to disk" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "SCARD" + , "key" + , "Get the number of members in a set" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SDIFF" + , "(key)+" + , "Subtract multiple sets" + , COMMAND_GROUP_SET + , "0.100" } + + , { "SDIFFSTORE" + , "destination (key)+" + , "Subtract multiple sets and store the resulting set in a key" + , COMMAND_GROUP_SET + , "0.100" } + + , { "SELECT" + , "index" + , "Change the selected database for the current connection" + , COMMAND_GROUP_CONNECTION + , "0.07" } + + , { "SET" + , "key value" + , "Set the string value of a key" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "SETEX" + , "key timestamp value" + , "Set the value and expiration of a key" + , COMMAND_GROUP_STRING + , "1.3.10" } + + , { "SETNX" + , "key value" + , "Set the value of a key, only if the key does not exist" + , COMMAND_GROUP_STRING + , "0.07" } + + , { "SHUTDOWN" + , "-" + , "Synchronously save the dataset to disk and then shut down the server" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "SINTER" + , "(key)+" + , "Intersect multiple sets" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SINTERSTORE" + , "destination (key)+" + , "Intersect multiple sets and store the resulting set in a key" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SISMEMBER" + , "key member" + , "Determine if a given value is a member of a set" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SLAVEOF" + , "host port" + , "Make the server a slave of another instance, or promote it as master" + , COMMAND_GROUP_SERVER + , "0.100" } + + , { "SMEMBERS" + , "key" + , "Get all the members in a set" + , COMMAND_GROUP_SET + , "0.07" } + + , { "SMOVE" + , "source destination member" + , "Move a member from one set to another" + , COMMAND_GROUP_SET + , "0.091" } + + , { "SORT" + , "key (BY pattern)? (LIMIT start count)? (GET pattern)* (ASC|DESC)? (ALPHA)? (STORE destination)?" + , "Sort the elements in a list, set or sorted set" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "SPOP" + , "key" + , "Remove and return a random member from a set" + , COMMAND_GROUP_SET + , "0.101" } + + , { "SRANDMEMBER" + , "key" + , "Get a random member from a set" + , COMMAND_GROUP_SET + , "1.001" } + + , { "SREM" + , "key member" + , "Remove a member from a set" + , COMMAND_GROUP_SET + , "0.07" } + + , { "STRLEN" + , "key" + , "Get the length of the value stored in a key" + , COMMAND_GROUP_STRING + , "2.1.2" } + + , { "SUBSCRIBE" + , "channel" + , "Listen for messages published to the given channels" + , COMMAND_GROUP_PUBSUB + , "1.3.8" } + + , { "SUBSTR" + , "key start stop" + , "Get a substring of the string stored at a key" + , COMMAND_GROUP_STRING + , "1.3.4" } + + , { "SUNION" + , "(key)+" + , "Add multiple sets" + , COMMAND_GROUP_SET + , "0.091" } + + , { "SUNIONSTORE" + , "destination (key)+" + , "Add multiple sets and store the resulting set in a key" + , COMMAND_GROUP_SET + , "0.091" } + + , { "SYNC" + , "-" + , "Internal command used for replication" + , COMMAND_GROUP_SERVER + , "0.07" } + + , { "TTL" + , "key" + , "Get the time to live for a key" + , COMMAND_GROUP_GENERIC + , "0.100" } + + , { "TYPE" + , "key" + , "Determine the type stored at key" + , COMMAND_GROUP_GENERIC + , "0.07" } + + , { "UNSUBSCRIBE" + , "(channel)*" + , "Stop listening for messages posted to the given channels" + , COMMAND_GROUP_PUBSUB + , "1.3.8" } + + , { "UNWATCH" + , "-" + , "Forget about all watched keys" + , COMMAND_GROUP_TRANSACTIONS + , "2.1.0" } + + , { "WATCH" + , "(key)+" + , "Watch the given keys to determine execution of the MULTI/EXEC block" + , COMMAND_GROUP_TRANSACTIONS + , "2.1.0" } + + , { "ZADD" + , "key score member" + , "Add a member to a sorted set, or update its score if it already exists" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZCARD" + , "key" + , "Get the number of members in a sorted set" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZCOUNT" + , "key min max" + , "Count the members in a sorted set with scores within the given values" + , COMMAND_GROUP_SORTED_SET + , "1.3.3" } + + , { "ZINCRBY" + , "key increment member" + , "Increment the score of a member in a sorted set" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZINTERSTORE" + , "destination (key)+ (WEIGHTS weight)? (AGGREGATE SUM|MIN|MAX)?" + , "Intersect multiple sorted sets and store the resulting sorted set in a new key" + , COMMAND_GROUP_SORTED_SET + , "1.3.10" } + + , { "ZRANGE" + , "key start stop" + , "Return a range of members in a sorted set, by index" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZRANGEBYSCORE" + , "key min max" + , "Return a range of members in a sorted set, by score" + , COMMAND_GROUP_SORTED_SET + , "1.050" } + + , { "ZRANK" + , "key member" + , "Determine the index of a member in a sorted set" + , COMMAND_GROUP_SORTED_SET + , "1.3.4" } + + , { "ZREM" + , "key member" + , "Remove a member from a sorted set" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZREMRANGEBYRANK" + , "key start stop" + , "Remove all members in a sorted set within the given indexes" + , COMMAND_GROUP_SORTED_SET + , "1.3.4" } + + , { "ZREMRANGEBYSCORE" + , "key min max" + , "Remove all members in a sorted set within the given scores" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZREVRANGE" + , "key start stop" + , "Return a range of members in a sorted set, by index, with scores ordered from high to low" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZREVRANK" + , "key member" + , "Determine the index of a member in a sorted set, with scores ordered from high to low" + , COMMAND_GROUP_SORTED_SET + , "1.3.4" } + + , { "ZSCORE" + , "key member" + , "Get the score associated with the given member in a sorted set" + , COMMAND_GROUP_SORTED_SET + , "1.1" } + + , { "ZUNIONSTORE" + , "destination (key)+ (WEIGHTS weight)? (AGGREGATE SUM|MIN|MAX)?" + , "Add multiple sorted sets and store the resulting sorted set in a new key" + , COMMAND_GROUP_SORTED_SET + , "1.3.10" } +}; + +/* + * Output command help to stdout. + */ + +static void +output_command_help(struct command_help *help) { + printf("\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\n", help->name, help->params); + printf(" \x1b[33msummary:\x1b[0m %s\n", help->summary); + printf(" \x1b[33msince:\x1b[0m %s\n", help->since); + printf(" \x1b[33mgroup:\x1b[0m %s\n", command_group_type_names[help->group]); +} + +/* + * Return command group type by name string. + */ + +static command_group_type_t +command_group_type_by_name(const char *name) { + for (int i = 0; i < COMMAND_GROUP_LENGTH; ++i) { + const char *group = command_group_type_names[i]; + if (0 == strcasecmp(name, group)) return i; + } + return 0; +} + +/* + * Output group names. + */ + +static void +output_group_help() { + for (int i = 0; i < COMMAND_GROUP_LENGTH; ++i) { + if (COMMAND_GROUP_UNKNOWN == i) continue; + const char *group = command_group_type_names[i]; + printf(" \x1b[90m-\x1b[0m %s\n", group); + } +} + +/* + * Output all command help, filtering by group or command name. + */ + +static void +output_help(int argc, const char **argv) { + int len = sizeof(command_help) / sizeof(struct command_help); + + if (argc && 0 == strcasecmp("groups", argv[0])) { + output_group_help(); + return; + } + + command_group_type_t group = argc + ? command_group_type_by_name(argv[0]) + : COMMAND_GROUP_UNKNOWN; + + for (int i = 0; i < len; ++i) { + struct command_help help = command_help[i]; + if (argc && !group && 0 != strcasecmp(help.name, argv[0])) continue; + if (group && group != help.group) continue; + output_command_help(&help); + } + puts(""); +} diff --git a/src/redis-cli.c b/src/redis-cli.c index aa7306b4..6ae77545 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -44,6 +44,7 @@ #include "adlist.h" #include "zmalloc.h" #include "linenoise.h" +#include "help.h" #define REDIS_NOTUSED(V) ((void) V) @@ -248,22 +249,6 @@ static int selectDb(int fd) { return 0; } -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]; int fd, j, retval = 0; @@ -271,7 +256,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) { config.raw_output = !strcasecmp(command,"info"); if (!strcasecmp(command,"help")) { - showInteractiveHelp(); + output_help(--argc, ++argv); return 0; } if (!strcasecmp(command,"shutdown")) config.shutdown = 1; diff --git a/utils/generate-command-help.rb b/utils/generate-command-help.rb new file mode 100755 index 00000000..250a2159 --- /dev/null +++ b/utils/generate-command-help.rb @@ -0,0 +1,56 @@ +#!/usr/bin/env ruby + +require 'net/http' +require 'net/https' +require 'json' +require 'uri' + +dest = ARGV[0] +tmpl = File.read './utils/help.h' + +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 +res = client.get url.path + +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 += arg['optional'] ? '*' : '+' + elsif arg['optional'] + name = "(#{name})?" + end + name +end + +def arguments command + return '-' unless command['arguments'] + command['arguments'].map do |arg| + argument arg + end.join ' ' +end + +case res +when Net::HTTPSuccess + first = true + commands = JSON.parse(res.body) + c = commands.map do |key, command| + buf = if first + first = false + ' ' + else + "\n ," + end + buf += " { \"#{key}\"\n" + + " , \"#{arguments(command)}\"\n" + + " , \"#{command['summary']}\"\n" + + " , COMMAND_GROUP_#{command['group'].upcase}\n" + + " , \"#{command['since']}\" }" + end.join("\n") + puts "\n// Auto-generated, do not edit.\n" + tmpl.sub('__COMMANDS__', c) +else + res.error! +end \ No newline at end of file diff --git a/utils/help.h b/utils/help.h new file mode 100644 index 00000000..dc7f270d --- /dev/null +++ b/utils/help.h @@ -0,0 +1,119 @@ + +#include +#include + +/* + * List command groups. + */ + +#define GROUPS \ + G(UNKNOWN, "unknown") \ + G(SET, "set") \ + G(LIST, "list") \ + G(HASH, "hash") \ + G(GENERIC, "generic") \ + G(PUBSUB, "pubsub") \ + G(STRING, "string") \ + G(SERVER, "server") \ + G(CONNECTION, "connection") \ + G(TRANSACTIONS, "transactions") \ + G(SORTED_SET, "sorted_set") + +/* + * Command group types. + */ + +typedef enum { + #define G(GROUP, _) COMMAND_GROUP_##GROUP, + GROUPS + #undef G + COMMAND_GROUP_LENGTH +} command_group_type_t; + +/* + * Command group type names. + */ + +static char *command_group_type_names[] = { + #define G(_, STR) STR, + GROUPS + #undef G +}; + +/* + * Command help struct. + */ + +struct command_help { + char *name; + char *params; + char *summary; + command_group_type_t group; + char *since; +} command_help[] = { + __COMMANDS__ +}; + +/* + * Output command help to stdout. + */ + +static void +output_command_help(struct command_help *help) { + printf("\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\n", help->name, help->params); + printf(" \x1b[33msummary:\x1b[0m %s\n", help->summary); + printf(" \x1b[33msince:\x1b[0m %s\n", help->since); + printf(" \x1b[33mgroup:\x1b[0m %s\n", command_group_type_names[help->group]); +} + +/* + * Return command group type by name string. + */ + +static command_group_type_t +command_group_type_by_name(const char *name) { + for (int i = 0; i < COMMAND_GROUP_LENGTH; ++i) { + const char *group = command_group_type_names[i]; + if (0 == strcasecmp(name, group)) return i; + } + return 0; +} + +/* + * Output group names. + */ + +static void +output_group_help() { + for (int i = 0; i < COMMAND_GROUP_LENGTH; ++i) { + if (COMMAND_GROUP_UNKNOWN == i) continue; + const char *group = command_group_type_names[i]; + printf(" \x1b[90m-\x1b[0m %s\n", group); + } +} + +/* + * Output all command help, filtering by group or command name. + */ + +static void +output_help(int argc, const char **argv) { + int len = sizeof(command_help) / sizeof(struct command_help); + + if (argc && 0 == strcasecmp("groups", argv[0])) { + output_group_help(); + return; + } + + command_group_type_t group = argc + ? command_group_type_by_name(argv[0]) + : COMMAND_GROUP_UNKNOWN; + + for (int i = 0; i < len; ++i) { + struct command_help help = command_help[i]; + if (argc && !group && 0 != strcasecmp(help.name, argv[0])) continue; + if (group && group != help.group) continue; + output_command_help(&help); + } + puts(""); +} \ No newline at end of file