diff options
Diffstat (limited to 'examples/redis-unstable/utils/generate-commands-json.py')
| -rwxr-xr-x | examples/redis-unstable/utils/generate-commands-json.py | 148 |
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 | ||
| 2 | import argparse | ||
| 3 | import json | ||
| 4 | import os | ||
| 5 | import sys | ||
| 6 | import subprocess | ||
| 7 | from collections import OrderedDict | ||
| 8 | |||
| 9 | |||
| 10 | def 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 | |||
| 15 | def 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 | |||
| 21 | def 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 | |||
| 29 | def convert_keyspec(spec): | ||
| 30 | """Transform a key spec.""" | ||
| 31 | spec.update(convert_flags_to_boolean_dict(spec.pop('flags', []))) | ||
| 32 | return spec | ||
| 33 | |||
| 34 | |||
| 35 | def 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 | ||
| 106 | srcdir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/../src") | ||
| 107 | |||
| 108 | # MAIN | ||
| 109 | if __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) | ||
