#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# script to produce rst file from program's help output.

import sys
import re
import argparse

arg_indent = ' ' * 14

def help2man(infile):
    # We assume that first line is usage line like this:
    #
    # Usage: nghttp [OPTIONS]... URI...
    #
    # The second line is description of the command.  Multiple lines
    # are permitted.  The blank line signals the end of this section.
    # After that, we parses positional and optional arguments.
    #
    # The positional argument is enclosed with < and >:
    #
    # <PRIVATE_KEY>
    #
    # We may describe default behavior without any options by encoding
    # ( and ):
    #
    # (default mode)
    #
    # "Options:" is treated specially and produces "OPTIONS" section.
    # We allow subsection under OPTIONS.  Lines not starting with (, <
    # and Options: are treated as subsection name and produces section
    # one level down:
    #
    # TLS/SSL:
    #
    # The above is an example of subsection.
    #
    # The description of arguments must be indented by len(arg_indent)
    # characters.  The default value should be placed in separate line
    # and should be start with "Default: " after indentation.

    line = infile.readline().strip()
    m = re.match(r'^Usage: (.*)', line)
    if not m:
        print('usage line is invalid.  Expected following lines:')
        print('Usage: cmdname ...')
        sys.exit(1)
    synopsis = m.group(1).split(' ', 1)
    if len(synopsis) == 2:
        cmdname, args = synopsis
    else:
        cmdname, args = synopsis[0], ''

    description = []
    for line in infile:
        line = line.strip()
        if not line:
            break
        description.append(line)

    print('''
.. GENERATED by help2rst.py.  DO NOT EDIT DIRECTLY.

.. program:: {cmdname}

{cmdname}(1)
{cmdnameunderline}

SYNOPSIS
--------

**{cmdname}** {args}

DESCRIPTION
-----------

{description}
'''.format(cmdname=cmdname, args=args,
           cmdnameunderline='=' * (len(cmdname) + 3),
           synopsis=synopsis, description=format_text('\n'.join(description))))

    in_arg = False
    in_footer = False

    for line in infile:
        line = line.rstrip()

        if not line.strip() and in_arg:
            print()
            continue
        if line.startswith('   ') and in_arg:
            if not line.startswith(arg_indent):
                sys.stderr.write('warning: argument description is not indented correctly.  We need {} spaces as indentation.\n'.format(len(arg_indent)))
            print('{}'.format(format_arg_text(line[len(arg_indent):])))
            continue

        if in_arg:
            print()
            in_arg = False

        if line == '--':
            in_footer = True
            continue

        if in_footer:
            print(line.strip())
            continue

        if line == 'Options:':
            print('OPTIONS')
            print('-------')
            print()
            continue

        if line.startswith('  <'):
            # positional argument
            m = re.match(r'^(?:\s+)([a-zA-Z0-9-_<>]+)(.*)', line)
            argname, rest = m.group(1), m.group(2)
            print('.. describe:: {}'.format(argname))
            print()
            print('{}'.format(format_arg_text(rest.strip())))
            in_arg = True
            continue

        if line.startswith('  ('):
            # positional argument
            m = re.match(r'^(?:\s+)(\([a-zA-Z0-9-_<> ]+\))(.*)', line)
            argname, rest = m.group(1), m.group(2)
            print('.. describe:: {}'.format(argname))
            print()
            print('{}'.format(format_arg_text(rest.strip())))
            in_arg = True
            continue

        if line.startswith('  -'):
            # optional argument
            m = re.match(
                r'^(?:\s+)(-\S+?(?:, -\S+?)*)($| .*)',
                line)
            argname, rest = m.group(1), m.group(2)
            print('.. option:: {}'.format(argname))
            print()
            rest = rest.strip()
            if len(rest):
                print('{}'.format(format_arg_text(rest)))
            in_arg = True
            continue

        if not line.startswith(' ') and line.endswith(':'):
            # subsection
            subsec = line.strip()[:-1]
            print('{}'.format(subsec))
            print('{}'.format('~' * len(subsec)))
            print()
            continue

        print(line.strip())

def format_text(text):
    # escape *, but don't escape * if it is used as bullet list.
    m = re.match(r'^\s*\*\s+', text)
    if m:
        text = text[:len(m.group(0))] + re.sub(r'\*', r'\*', text[len(m.group(0)):])
    else:
        text = re.sub(r'\*', r'\*', text)
    # markup option reference
    text = re.sub(r'(^|\s)(-[a-zA-Z]|--[a-zA-Z0-9-]+)',
                  r'\1:option:`\2`', text)
    # sphinx does not like markup like ':option:`-f`='.  We need
    # backslash between ` and =.
    text = re.sub(r'(:option:`.*?`)(\S)', r'\1\\\2', text)
    # file path should be italic
    text = re.sub(r'(^|\s|\'|")(/[^\s\'"]*)', r'\1*\2*', text)
    return text

def format_arg_text(text):
    if text.strip().startswith('Default: '):
        return '\n    ' + re.sub(r'^(\s*Default: )(.*)$', r'\1``\2``', text)
    return '    {}'.format(format_text(text))

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Produces rst document from help output.')
    parser.add_argument('-i', '--include', metavar='FILE',
                        help='include content of <FILE> as verbatim.  It should be ReST formatted text.')
    args = parser.parse_args()
    help2man(sys.stdin)
    if args.include:
        print()
        with open(args.include) as f:
            sys.stdout.write(f.read())
