#!/usr/bin/env python3 import re import subprocess as sp import sys import json re_full = re.compile(r'(?P^.*?$)(?P.*?)(^Argument.*?$(?P.*?))?(^Result[^\n]*?:\s*$(?P.*?))?(^Exampl.*?$(?P.*))?', re.DOTALL | re.MULTILINE) re_argline = re.compile(r'^("?)(?P.*?)\1\s+\((?P.*?)\)\s*(?P.*)$', re.DOTALL) def get_type(arg_type, full_line): if arg_type is None: return 'string' arg_type = arg_type.lower() if 'numeric' in arg_type: return 'number' if 'bool' in arg_type: return 'boolean' if 'string' in arg_type: return 'string' if 'object' in arg_type: return 'object' raise Exception('Not implemented: ' + arg_type) def parse_params(args): arguments = [] if args: for line in re.split('\s*\d+\.\s+', args, re.DOTALL): if not line or not line.strip() or line.strip().startswith('None'): continue arg_parsed = re_argline.fullmatch(line) if arg_parsed is None: raise Exception("Unparsable argument: " + line) arg_name, arg_type, arg_desc = arg_parsed.group('name', 'type', 'desc') if not arg_type: raise Exception('Not implemented: ' + arg_type) arg_required = 'required' in arg_type or 'optional' not in arg_type arg_refined_type = get_type(arg_type, line) arg_desc = re.sub('\s+', ' ', arg_desc.strip()) if arg_desc else [] arguments.append({ 'name': arg_name, 'type': arg_refined_type, 'description': arg_desc, 'is_required': arg_required }) return arguments def process_examples(examples: str): if not examples: return [] examples = examples.strip() splits = examples.split('\n') result = [] inner = {} for s in splits: if not s: continue if '> curl' in s: inner['curl'] = s.strip() elif '> lbrycrd' in s: inner['cli'] = s.strip() else: if 'title' in inner: result.append(inner) inner = {} inner['title'] = s.strip() result.append(inner) return result def get_api(section_name, command, command_help): parsed = re_full.fullmatch(command_help) if parsed is None: raise Exception('Unable to resolve help format for ' + command) name, desc, args, resl, exmp = parsed.group('name', 'desc', 'args', 'resl', 'exmp') arguments = parse_params(args) cmd_desc = re.sub('\s+', ' ', desc.strip()) if desc else '' examp_desc = process_examples(exmp) cmd_resl = resl.strip() if resl else None ret = { 'name': command, 'namespace': section_name, 'description': cmd_desc, 'arguments': arguments, 'examples': examp_desc } if cmd_resl is not None: ret['returns'] = cmd_resl return ret def write_api(): if len(sys.argv) < 2: print("Missing required argument: ", file=sys.stderr) sys.exit(1) cli_tool = sys.argv[1] result = sp.run([cli_tool, "help"], stdout=sp.PIPE, universal_newlines=True) commands = result.stdout sections = re.split('^==\s*(.*?)\s*==$', commands, flags=re.MULTILINE) apis = [] for section in sections: if not section: continue lines = section.splitlines() if len(lines) == 1: section_name = lines[0] continue for command in sorted(lines[1:]): if not command: continue command = command.split(' ')[0] result = sp.run([cli_tool, "help", command], stdout=sp.PIPE, universal_newlines=True) apis.append(get_api(section_name, command, result.stdout)) print(json.dumps(apis, indent=4)) if __name__ == '__main__': write_api()