12c593315Sopenharmony_ci#!/usr/bin/env python3 22c593315Sopenharmony_ci# -*- coding: utf-8 -*- 32c593315Sopenharmony_ci 42c593315Sopenharmony_ci# script to produce rst file from program's help output. 52c593315Sopenharmony_ci 62c593315Sopenharmony_ciimport sys 72c593315Sopenharmony_ciimport re 82c593315Sopenharmony_ciimport argparse 92c593315Sopenharmony_ci 102c593315Sopenharmony_ciarg_indent = ' ' * 14 112c593315Sopenharmony_ci 122c593315Sopenharmony_cidef help2man(infile): 132c593315Sopenharmony_ci # We assume that first line is usage line like this: 142c593315Sopenharmony_ci # 152c593315Sopenharmony_ci # Usage: nghttp [OPTIONS]... URI... 162c593315Sopenharmony_ci # 172c593315Sopenharmony_ci # The second line is description of the command. Multiple lines 182c593315Sopenharmony_ci # are permitted. The blank line signals the end of this section. 192c593315Sopenharmony_ci # After that, we parses positional and optional arguments. 202c593315Sopenharmony_ci # 212c593315Sopenharmony_ci # The positional argument is enclosed with < and >: 222c593315Sopenharmony_ci # 232c593315Sopenharmony_ci # <PRIVATE_KEY> 242c593315Sopenharmony_ci # 252c593315Sopenharmony_ci # We may describe default behavior without any options by encoding 262c593315Sopenharmony_ci # ( and ): 272c593315Sopenharmony_ci # 282c593315Sopenharmony_ci # (default mode) 292c593315Sopenharmony_ci # 302c593315Sopenharmony_ci # "Options:" is treated specially and produces "OPTIONS" section. 312c593315Sopenharmony_ci # We allow subsection under OPTIONS. Lines not starting with (, < 322c593315Sopenharmony_ci # and Options: are treated as subsection name and produces section 332c593315Sopenharmony_ci # one level down: 342c593315Sopenharmony_ci # 352c593315Sopenharmony_ci # TLS/SSL: 362c593315Sopenharmony_ci # 372c593315Sopenharmony_ci # The above is an example of subsection. 382c593315Sopenharmony_ci # 392c593315Sopenharmony_ci # The description of arguments must be indented by len(arg_indent) 402c593315Sopenharmony_ci # characters. The default value should be placed in separate line 412c593315Sopenharmony_ci # and should be start with "Default: " after indentation. 422c593315Sopenharmony_ci 432c593315Sopenharmony_ci line = infile.readline().strip() 442c593315Sopenharmony_ci m = re.match(r'^Usage: (.*)', line) 452c593315Sopenharmony_ci if not m: 462c593315Sopenharmony_ci print('usage line is invalid. Expected following lines:') 472c593315Sopenharmony_ci print('Usage: cmdname ...') 482c593315Sopenharmony_ci sys.exit(1) 492c593315Sopenharmony_ci synopsis = m.group(1).split(' ', 1) 502c593315Sopenharmony_ci if len(synopsis) == 2: 512c593315Sopenharmony_ci cmdname, args = synopsis 522c593315Sopenharmony_ci else: 532c593315Sopenharmony_ci cmdname, args = synopsis[0], '' 542c593315Sopenharmony_ci 552c593315Sopenharmony_ci description = [] 562c593315Sopenharmony_ci for line in infile: 572c593315Sopenharmony_ci line = line.strip() 582c593315Sopenharmony_ci if not line: 592c593315Sopenharmony_ci break 602c593315Sopenharmony_ci description.append(line) 612c593315Sopenharmony_ci 622c593315Sopenharmony_ci print(''' 632c593315Sopenharmony_ci.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. 642c593315Sopenharmony_ci 652c593315Sopenharmony_ci.. program:: {cmdname} 662c593315Sopenharmony_ci 672c593315Sopenharmony_ci{cmdname}(1) 682c593315Sopenharmony_ci{cmdnameunderline} 692c593315Sopenharmony_ci 702c593315Sopenharmony_ciSYNOPSIS 712c593315Sopenharmony_ci-------- 722c593315Sopenharmony_ci 732c593315Sopenharmony_ci**{cmdname}** {args} 742c593315Sopenharmony_ci 752c593315Sopenharmony_ciDESCRIPTION 762c593315Sopenharmony_ci----------- 772c593315Sopenharmony_ci 782c593315Sopenharmony_ci{description} 792c593315Sopenharmony_ci'''.format(cmdname=cmdname, args=args, 802c593315Sopenharmony_ci cmdnameunderline='=' * (len(cmdname) + 3), 812c593315Sopenharmony_ci synopsis=synopsis, description=format_text('\n'.join(description)))) 822c593315Sopenharmony_ci 832c593315Sopenharmony_ci in_arg = False 842c593315Sopenharmony_ci in_footer = False 852c593315Sopenharmony_ci 862c593315Sopenharmony_ci for line in infile: 872c593315Sopenharmony_ci line = line.rstrip() 882c593315Sopenharmony_ci 892c593315Sopenharmony_ci if not line.strip() and in_arg: 902c593315Sopenharmony_ci print() 912c593315Sopenharmony_ci continue 922c593315Sopenharmony_ci if line.startswith(' ') and in_arg: 932c593315Sopenharmony_ci if not line.startswith(arg_indent): 942c593315Sopenharmony_ci sys.stderr.write('warning: argument description is not indented correctly. We need {} spaces as indentation.\n'.format(len(arg_indent))) 952c593315Sopenharmony_ci print('{}'.format(format_arg_text(line[len(arg_indent):]))) 962c593315Sopenharmony_ci continue 972c593315Sopenharmony_ci 982c593315Sopenharmony_ci if in_arg: 992c593315Sopenharmony_ci print() 1002c593315Sopenharmony_ci in_arg = False 1012c593315Sopenharmony_ci 1022c593315Sopenharmony_ci if line == '--': 1032c593315Sopenharmony_ci in_footer = True 1042c593315Sopenharmony_ci continue 1052c593315Sopenharmony_ci 1062c593315Sopenharmony_ci if in_footer: 1072c593315Sopenharmony_ci print(line.strip()) 1082c593315Sopenharmony_ci continue 1092c593315Sopenharmony_ci 1102c593315Sopenharmony_ci if line == 'Options:': 1112c593315Sopenharmony_ci print('OPTIONS') 1122c593315Sopenharmony_ci print('-------') 1132c593315Sopenharmony_ci print() 1142c593315Sopenharmony_ci continue 1152c593315Sopenharmony_ci 1162c593315Sopenharmony_ci if line.startswith(' <'): 1172c593315Sopenharmony_ci # positional argument 1182c593315Sopenharmony_ci m = re.match(r'^(?:\s+)([a-zA-Z0-9-_<>]+)(.*)', line) 1192c593315Sopenharmony_ci argname, rest = m.group(1), m.group(2) 1202c593315Sopenharmony_ci print('.. describe:: {}'.format(argname)) 1212c593315Sopenharmony_ci print() 1222c593315Sopenharmony_ci print('{}'.format(format_arg_text(rest.strip()))) 1232c593315Sopenharmony_ci in_arg = True 1242c593315Sopenharmony_ci continue 1252c593315Sopenharmony_ci 1262c593315Sopenharmony_ci if line.startswith(' ('): 1272c593315Sopenharmony_ci # positional argument 1282c593315Sopenharmony_ci m = re.match(r'^(?:\s+)(\([a-zA-Z0-9-_<> ]+\))(.*)', line) 1292c593315Sopenharmony_ci argname, rest = m.group(1), m.group(2) 1302c593315Sopenharmony_ci print('.. describe:: {}'.format(argname)) 1312c593315Sopenharmony_ci print() 1322c593315Sopenharmony_ci print('{}'.format(format_arg_text(rest.strip()))) 1332c593315Sopenharmony_ci in_arg = True 1342c593315Sopenharmony_ci continue 1352c593315Sopenharmony_ci 1362c593315Sopenharmony_ci if line.startswith(' -'): 1372c593315Sopenharmony_ci # optional argument 1382c593315Sopenharmony_ci m = re.match( 1392c593315Sopenharmony_ci r'^(?:\s+)(-\S+?(?:, -\S+?)*)($| .*)', 1402c593315Sopenharmony_ci line) 1412c593315Sopenharmony_ci argname, rest = m.group(1), m.group(2) 1422c593315Sopenharmony_ci print('.. option:: {}'.format(argname)) 1432c593315Sopenharmony_ci print() 1442c593315Sopenharmony_ci rest = rest.strip() 1452c593315Sopenharmony_ci if len(rest): 1462c593315Sopenharmony_ci print('{}'.format(format_arg_text(rest))) 1472c593315Sopenharmony_ci in_arg = True 1482c593315Sopenharmony_ci continue 1492c593315Sopenharmony_ci 1502c593315Sopenharmony_ci if not line.startswith(' ') and line.endswith(':'): 1512c593315Sopenharmony_ci # subsection 1522c593315Sopenharmony_ci subsec = line.strip()[:-1] 1532c593315Sopenharmony_ci print('{}'.format(subsec)) 1542c593315Sopenharmony_ci print('{}'.format('~' * len(subsec))) 1552c593315Sopenharmony_ci print() 1562c593315Sopenharmony_ci continue 1572c593315Sopenharmony_ci 1582c593315Sopenharmony_ci print(line.strip()) 1592c593315Sopenharmony_ci 1602c593315Sopenharmony_cidef format_text(text): 1612c593315Sopenharmony_ci # escape *, but don't escape * if it is used as bullet list. 1622c593315Sopenharmony_ci m = re.match(r'^\s*\*\s+', text) 1632c593315Sopenharmony_ci if m: 1642c593315Sopenharmony_ci text = text[:len(m.group(0))] + re.sub(r'\*', r'\*', text[len(m.group(0)):]) 1652c593315Sopenharmony_ci else: 1662c593315Sopenharmony_ci text = re.sub(r'\*', r'\*', text) 1672c593315Sopenharmony_ci # markup option reference 1682c593315Sopenharmony_ci text = re.sub(r'(^|\s)(-[a-zA-Z]|--[a-zA-Z0-9-]+)', 1692c593315Sopenharmony_ci r'\1:option:`\2`', text) 1702c593315Sopenharmony_ci # sphinx does not like markup like ':option:`-f`='. We need 1712c593315Sopenharmony_ci # backslash between ` and =. 1722c593315Sopenharmony_ci text = re.sub(r'(:option:`.*?`)(\S)', r'\1\\\2', text) 1732c593315Sopenharmony_ci # file path should be italic 1742c593315Sopenharmony_ci text = re.sub(r'(^|\s|\'|")(/[^\s\'"]*)', r'\1*\2*', text) 1752c593315Sopenharmony_ci return text 1762c593315Sopenharmony_ci 1772c593315Sopenharmony_cidef format_arg_text(text): 1782c593315Sopenharmony_ci if text.strip().startswith('Default: '): 1792c593315Sopenharmony_ci return '\n ' + re.sub(r'^(\s*Default: )(.*)$', r'\1``\2``', text) 1802c593315Sopenharmony_ci return ' {}'.format(format_text(text)) 1812c593315Sopenharmony_ci 1822c593315Sopenharmony_ciif __name__ == '__main__': 1832c593315Sopenharmony_ci parser = argparse.ArgumentParser( 1842c593315Sopenharmony_ci description='Produces rst document from help output.') 1852c593315Sopenharmony_ci parser.add_argument('-i', '--include', metavar='FILE', 1862c593315Sopenharmony_ci help='include content of <FILE> as verbatim. It should be ReST formatted text.') 1872c593315Sopenharmony_ci args = parser.parse_args() 1882c593315Sopenharmony_ci help2man(sys.stdin) 1892c593315Sopenharmony_ci if args.include: 1902c593315Sopenharmony_ci print() 1912c593315Sopenharmony_ci with open(args.include) as f: 1922c593315Sopenharmony_ci sys.stdout.write(f.read()) 193