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