aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/utils/generate-commands-json.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/utils/generate-commands-json.py')
-rwxr-xr-xexamples/redis-unstable/utils/generate-commands-json.py148
1 files changed, 0 insertions, 148 deletions
diff --git a/examples/redis-unstable/utils/generate-commands-json.py b/examples/redis-unstable/utils/generate-commands-json.py
deleted file mode 100755
index ea18a79..0000000
--- a/examples/redis-unstable/utils/generate-commands-json.py
+++ /dev/null
@@ -1,148 +0,0 @@
1#!/usr/bin/env python3
2import argparse
3import json
4import os
5import sys
6import subprocess
7from collections import OrderedDict
8
9
10def convert_flags_to_boolean_dict(flags):
11 """Return a dict with a key set to `True` per element in the flags list."""
12 return {f: True for f in flags}
13
14
15def set_if_not_none_or_empty(dst, key, value):
16 """Set 'key' in 'dst' if 'value' is not `None` or an empty list."""
17 if value is not None and (type(value) is not list or len(value)):
18 dst[key] = value
19
20
21def convert_argument(arg):
22 """Transform an argument."""
23 arg.update(convert_flags_to_boolean_dict(arg.pop('flags', [])))
24 set_if_not_none_or_empty(arg, 'arguments',
25 [convert_argument(x) for x in arg.pop('arguments', [])])
26 return arg
27
28
29def convert_keyspec(spec):
30 """Transform a key spec."""
31 spec.update(convert_flags_to_boolean_dict(spec.pop('flags', [])))
32 return spec
33
34
35def convert_entry_to_objects_array(cmd, docs):
36 """Transform the JSON output of `COMMAND` to a friendlier format.
37
38 cmd is the output of `COMMAND` as follows:
39 1. Name (lower case, e.g. "lolwut")
40 2. Arity
41 3. Flags
42 4-6. First/last/step key specification (deprecated as of Redis v7.0)
43 7. ACL categories
44 8. hints (as of Redis 7.0)
45 9. key-specs (as of Redis 7.0)
46 10. subcommands (as of Redis 7.0)
47
48 docs is the output of `COMMAND DOCS`, which holds a map of additional metadata
49
50 This returns a list with a dict for the command and per each of its
51 subcommands. Each dict contains one key, the command's full name, with a
52 value of a dict that's set with the command's properties and meta
53 information."""
54 assert len(cmd) >= 9
55 obj = {}
56 rep = [obj]
57 name = cmd[0].upper()
58 arity = cmd[1]
59 command_flags = cmd[2]
60 acl_categories = cmd[6]
61 hints = cmd[7]
62 keyspecs = cmd[8]
63 subcommands = cmd[9] if len(cmd) > 9 else []
64 key = name.replace('|', ' ')
65
66 subcommand_docs = docs.pop('subcommands', [])
67 rep.extend([convert_entry_to_objects_array(x, subcommand_docs[x[0]])[0] for x in subcommands])
68
69 # The command's value is ordered so the interesting stuff that we care about
70 # is at the start. Optional `None` and empty list values are filtered out.
71 value = OrderedDict()
72 group = docs.pop('group')
73 if group == 'module':
74 set_if_not_none_or_empty(value, 'summary', docs.pop('summary', None))
75 set_if_not_none_or_empty(value, 'since', docs.pop('since', None))
76 else:
77 # "summary" and "since" are required for all non-module commands
78 value['summary'] = docs.pop('summary')
79 value['since'] = docs.pop('since')
80 value['group'] = group
81 set_if_not_none_or_empty(value, 'complexity', docs.pop('complexity', None))
82 set_if_not_none_or_empty(value, 'deprecated_since', docs.pop('deprecated_since', None))
83 set_if_not_none_or_empty(value, 'replaced_by', docs.pop('replaced_by', None))
84 set_if_not_none_or_empty(value, 'history', docs.pop('history', []))
85 set_if_not_none_or_empty(value, 'acl_categories', acl_categories)
86 value['arity'] = arity
87 set_if_not_none_or_empty(value, 'key_specs',
88 [convert_keyspec(x) for x in keyspecs])
89 set_if_not_none_or_empty(value, 'arguments',
90 [convert_argument(x) for x in docs.pop('arguments', [])])
91 set_if_not_none_or_empty(value, 'command_flags', command_flags)
92 set_if_not_none_or_empty(value, 'doc_flags', docs.pop('doc_flags', []))
93 set_if_not_none_or_empty(value, 'hints', hints)
94
95 # All remaining docs key-value tuples, if any, are appended to the command
96 # to be future-proof.
97 while len(docs) > 0:
98 (k, v) = docs.popitem()
99 value[k] = v
100
101 obj[key] = value
102 return rep
103
104
105# Figure out where the sources are
106srcdir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/../src")
107
108# MAIN
109if __name__ == '__main__':
110 opts = {
111 'description': 'Transform the output from `redis-cli --json` using COMMAND and COMMAND DOCS to a single commands.json format.',
112 'epilog': f'Usage example: {sys.argv[0]} --cli src/redis-cli --port 6379 > commands.json'
113 }
114 parser = argparse.ArgumentParser(**opts)
115 parser.add_argument('--host', type=str, default='localhost')
116 parser.add_argument('--port', type=int, default=6379)
117 parser.add_argument('--cli', type=str, default='%s/redis-cli' % srcdir)
118 args = parser.parse_args()
119
120 payload = OrderedDict()
121 cmds = []
122
123 p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command'], stdout=subprocess.PIPE)
124 stdout, stderr = p.communicate()
125 commands = json.loads(stdout)
126
127 p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command', 'docs'],
128 stdout=subprocess.PIPE)
129 stdout, stderr = p.communicate()
130 docs = json.loads(stdout)
131
132 for entry in commands:
133 cmd = convert_entry_to_objects_array(entry, docs[entry[0]])
134 cmds.extend(cmd)
135
136 # The final output is a dict of all commands, ordered by name.
137 cmds.sort(key=lambda x: list(x.keys())[0])
138 for cmd in cmds:
139 name = list(cmd.keys())[0]
140 payload[name] = cmd[name]
141
142 # Print the final JSON output. If the output is piped and the pipe is closed (e.g., by 'less' or 'head'),
143 # catch BrokenPipeError to prevent a traceback and exit gracefully.
144 try:
145 print(json.dumps(payload, indent=4))
146 except BrokenPipeError:
147 sys.stderr.close()
148 sys.exit(0)