nghttp2/help2rst.py

195 lines
5.7 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# script to produce rst file from program's help output.
from __future__ import unicode_literals
2015-06-07 05:55:22 +02:00
from __future__ import print_function
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:
2015-06-07 05:55:22 +02:00
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)
2015-06-07 05:55:22 +02:00
print('''
.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY.
2015-04-17 16:13:42 +02:00
.. program:: {cmdname}
{cmdname}(1)
{cmdnameunderline}
SYNOPSIS
--------
**{cmdname}** {args}
DESCRIPTION
-----------
{description}
'''.format(cmdname=cmdname, args=args,
cmdnameunderline='=' * (len(cmdname) + 3),
2015-06-07 05:55:22 +02:00
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:
2015-06-07 05:55:22 +02:00
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)))
2015-06-07 05:55:22 +02:00
print('{}'.format(format_arg_text(line[len(arg_indent):])))
continue
if in_arg:
2015-06-07 05:55:22 +02:00
print()
in_arg = False
if line == '--':
in_footer = True
continue
if in_footer:
2015-06-07 05:55:22 +02:00
print(line.strip())
continue
if line == 'Options:':
2015-06-07 05:55:22 +02:00
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)
2015-06-07 05:55:22 +02:00
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)
2015-06-07 05:55:22 +02:00
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)
2015-06-07 05:55:22 +02:00
print('.. option:: {}'.format(argname))
print()
rest = rest.strip()
if len(rest):
2015-06-07 05:55:22 +02:00
print('{}'.format(format_arg_text(rest)))
in_arg = True
continue
if not line.startswith(' ') and line.endswith(':'):
# subsection
subsec = line.strip()[:-1]
2015-06-07 05:55:22 +02:00
print('{}'.format(subsec))
print('{}'.format('~' * len(subsec)))
print()
continue
2015-06-07 05:55:22 +02:00
print(line.strip())
def format_text(text):
2016-10-09 11:39:32 +02:00
# 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
2016-07-16 12:07:31 +02:00
text = re.sub(r'(^|\s)(-[a-zA-Z0-9]|--[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:
2015-06-07 05:55:22 +02:00
print()
with open(args.include) as f:
sys.stdout.write(f.read())