summaryrefslogtreecommitdiff
path: root/man/make_man_page.py
blob: c7060e728e8aacaa62ddff8f5dbafd924d4bb20a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env python

# Creates a man page from a C file.

# first argument if present is path to cmark dynamic library

# Comments beginning with `/**` are treated as Groff man, except that
# 'this' is converted to \fIthis\f[], and ''this'' to \fBthis\f[].

# Non-blank lines immediately following a man page comment are treated
# as function signatures or examples and parsed into .Ft, .Fo, .Fa, .Fc. The
# immediately preceding man documentation chunk is printed after the example
# as a comment on it.

# That's about it!

import sys, re, os, platform
from datetime import date
from ctypes import CDLL, c_char_p, c_long, c_void_p

sysname = platform.system()

if sysname == 'Darwin':
    cmark = CDLL("build/src/libcmark.dylib")
else:
    cmark = CDLL("build/src/libcmark.so")

parse_document = cmark.cmark_parse_document
parse_document.restype = c_void_p
parse_document.argtypes = [c_char_p, c_long]

render_man = cmark.cmark_render_man
render_man.restype = c_char_p
render_man.argtypes = [c_void_p, c_long, c_long]

def md2man(text):
    if sys.version_info >= (3,0):
        textbytes = text.encode('utf-8')
        textlen = len(textbytes)
        return render_man(parse_document(textbytes, textlen), 0, 65).decode('utf-8')
    else:
        textbytes = text
        textlen = len(text)
        return render_man(parse_document(textbytes, textlen), 0, 72)

comment_start_re = re.compile('^\/\*\* ?')
comment_delim_re = re.compile('^[/ ]\** ?')
comment_end_re = re.compile('^ \**\/')
function_re = re.compile('^ *(?:CMARK_EXPORT\s+)?(?P<type>(?:const\s+)?\w+(?:\s*[*])?)\s*(?P<name>\w+)\s*\((?P<args>[^)]*)\)')
blank_re = re.compile('^\s*$')
macro_re = re.compile('CMARK_EXPORT *')
typedef_start_re = re.compile('typedef.*{$')
typedef_end_re = re.compile('}')
single_quote_re = re.compile("(?<!\w)'([^']+)'(?!\w)")
double_quote_re = re.compile("(?<!\w)''([^']+)''(?!\w)")

def handle_quotes(s):
    return re.sub(double_quote_re, '**\g<1>**', re.sub(single_quote_re, '*\g<1>*', s))

typedef = False
mdlines = []
chunk = []
sig = []

if len(sys.argv) > 1:
    sourcefile = sys.argv[1]
else:
    print("Usage:  make_man_page.py sourcefile")
    exit(1)

with open(sourcefile, 'r') as cmarkh:
    state = 'default'
    for line in cmarkh:
        # state transition
        oldstate = state
        if comment_start_re.match(line):
            state = 'man'
        elif comment_end_re.match(line) and state == 'man':
            continue
        elif comment_delim_re.match(line) and state == 'man':
            state = 'man'
        elif not typedef and blank_re.match(line):
            state = 'default'
        elif typedef and typedef_end_re.match(line):
            typedef = False
        elif state == 'man':
            state = 'signature'
            typedef = typedef_start_re.match(line)

        # handle line
        if state == 'man':
            chunk.append(handle_quotes(re.sub(comment_delim_re, '', line)))
        elif state == 'signature':
            ln = re.sub(macro_re, '', line)
            if typedef or not re.match(blank_re, ln):
                sig.append(ln)
        elif oldstate == 'signature' and state != 'signature':
            if len(mdlines) > 0 and mdlines[-1] != '\n':
                mdlines.append('\n')
            rawsig = ''.join(sig)
            m = function_re.match(rawsig)
            mdlines.append('.PP\n')
            if m:
                mdlines.append('\\fI' + m.group('type') + '\\f[]' + ' ')
                mdlines.append('\\fB' + m.group('name') + '\\f[]' + '(')
                first = True
                for argument in re.split(',', m.group('args')):
                    if not first:
                        mdlines.append(', ')
                    first = False
                    mdlines.append('\\fI' + argument.strip() + '\\f[]')
                mdlines.append(')\n')
            else:
                mdlines.append('.nf\n\\fC\n.RS 0n\n')
                mdlines += sig
                mdlines.append('.RE\n\\f[]\n.fi\n')
            if len(mdlines) > 0 and mdlines[-1] != '\n':
                mdlines.append('\n')
            mdlines += md2man(''.join(chunk))
            mdlines.append('\n')
            chunk = []
            sig = []
        elif oldstate == 'man' and state != 'signature':
            if len(mdlines) > 0 and mdlines[-1] != '\n':
                mdlines.append('\n')
            mdlines += md2man(''.join(chunk)) # add man chunk
            chunk = []
            mdlines.append('\n')

sys.stdout.write('.TH ' + os.path.basename(sourcefile).replace('.h','') + ' 3 "' + date.today().strftime('%B %d, %Y') + '" "LOCAL" "Library Functions Manual"\n')
sys.stdout.write(''.join(mdlines))