1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# nghttp2 - HTTP/2 C Library
4#
5# Copyright (c) 2020 nghttp2 contributors
6# Copyright (c) 2020 ngtcp2 contributors
7# Copyright (c) 2012 Tatsuhiro Tsujikawa
8#
9# Permission is hereby granted, free of charge, to any person obtaining
10# a copy of this software and associated documentation files (the
11# "Software"), to deal in the Software without restriction, including
12# without limitation the rights to use, copy, modify, merge, publish,
13# distribute, sublicense, and/or sell copies of the Software, and to
14# permit persons to whom the Software is furnished to do so, subject to
15# the following conditions:
16
17# The above copyright notice and this permission notice shall be
18# included in all copies or substantial portions of the Software.
19
20# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28# Generates API reference from C source code.
29
30import re, sys, argparse, os.path
31
32class FunctionDoc:
33    def __init__(self, name, content, domain, filename):
34        self.name = name
35        self.content = content
36        self.domain = domain
37        if self.domain == 'function':
38            self.funcname = re.search(r'(nghttp2_[^ )]+)\(', self.name).group(1)
39        self.filename = filename
40
41    def write(self, out):
42        out.write('.. {}:: {}\n'.format(self.domain, self.name))
43        out.write('\n')
44        for line in self.content:
45            out.write('    {}\n'.format(line))
46
47class StructDoc:
48    def __init__(self, name, content, members, member_domain):
49        self.name = name
50        self.content = content
51        self.members = members
52        self.member_domain = member_domain
53
54    def write(self, out):
55        if self.name:
56            out.write('.. type:: {}\n'.format(self.name))
57            out.write('\n')
58            for line in self.content:
59                out.write('    {}\n'.format(line))
60            out.write('\n')
61            for name, content in self.members:
62                out.write('    .. {}:: {}\n'.format(self.member_domain, name))
63                out.write('\n')
64                for line in content:
65                    out.write('        {}\n'.format(line))
66            out.write('\n')
67
68class EnumDoc:
69    def __init__(self, name, content, members):
70        self.name = name
71        self.content = content
72        self.members = members
73
74    def write(self, out):
75        if self.name:
76            out.write('.. type:: {}\n'.format(self.name))
77            out.write('\n')
78            for line in self.content:
79                out.write('    {}\n'.format(line))
80            out.write('\n')
81            for name, content in self.members:
82                out.write('    .. enum:: {}\n'.format(name))
83                out.write('\n')
84                for line in content:
85                    out.write('        {}\n'.format(line))
86            out.write('\n')
87
88class MacroDoc:
89    def __init__(self, name, content):
90        self.name = name
91        self.content = content
92
93    def write(self, out):
94        out.write('''.. macro:: {}\n'''.format(self.name))
95        out.write('\n')
96        for line in self.content:
97            out.write('    {}\n'.format(line))
98
99class MacroSectionDoc:
100    def __init__(self, content):
101        self.content = content
102
103    def write(self, out):
104        out.write('\n')
105        c = ' '.join(self.content).strip()
106        out.write(c)
107        out.write('\n')
108        out.write('-' * len(c))
109        out.write('\n\n')
110
111class TypedefDoc:
112    def __init__(self, name, content):
113        self.name = name
114        self.content = content
115
116    def write(self, out):
117        out.write('''.. type:: {}\n'''.format(self.name))
118        out.write('\n')
119        for line in self.content:
120            out.write('    {}\n'.format(line))
121
122def make_api_ref(infile):
123    macros = []
124    enums = []
125    types = []
126    functions = []
127    while True:
128        line = infile.readline()
129        if not line:
130            break
131        elif line == '/**\n':
132            line = infile.readline()
133            doctype = line.split()[1]
134            if doctype == '@function':
135                functions.append(process_function('function', infile))
136            elif doctype == '@functypedef':
137                types.append(process_function('type', infile))
138            elif doctype == '@struct' or doctype == '@union':
139                types.append(process_struct(infile))
140            elif doctype == '@enum':
141                enums.append(process_enum(infile))
142            elif doctype == '@macro':
143                macros.append(process_macro(infile))
144            elif doctype == '@macrosection':
145                macros.append(process_macrosection(infile))
146            elif doctype == '@typedef':
147                types.append(process_typedef(infile))
148    return macros, enums, types, functions
149
150def output(
151        title, indexfile, macrosfile, enumsfile, typesfile, funcsdir,
152        macros, enums, types, functions):
153    indexfile.write('''
154{title}
155{titledecoration}
156
157.. toctree::
158   :maxdepth: 1
159
160   {macros}
161   {enums}
162   {types}
163'''.format(
164    title=title, titledecoration='='*len(title),
165    macros=os.path.splitext(os.path.basename(macrosfile.name))[0],
166    enums=os.path.splitext(os.path.basename(enumsfile.name))[0],
167    types=os.path.splitext(os.path.basename(typesfile.name))[0],
168))
169
170    for doc in functions:
171        indexfile.write('   {}\n'.format(doc.funcname))
172
173    macrosfile.write('''
174Macros
175======
176''')
177    for doc in macros:
178        doc.write(macrosfile)
179
180    enumsfile.write('''
181Enums
182=====
183''')
184    for doc in enums:
185        doc.write(enumsfile)
186
187    typesfile.write('''
188Types (structs, unions and typedefs)
189====================================
190''')
191    for doc in types:
192        doc.write(typesfile)
193
194    for doc in functions:
195        with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f:
196            f.write('''
197{funcname}
198{secul}
199
200Synopsis
201--------
202
203*#include <nghttp2/{filename}>*
204
205'''.format(funcname=doc.funcname, secul='='*len(doc.funcname),
206           filename=doc.filename))
207            doc.write(f)
208
209def process_macro(infile):
210    content = read_content(infile)
211    line = infile.readline()
212    macro_name = line.split()[1]
213    return MacroDoc(macro_name, content)
214
215def process_macrosection(infile):
216    content = read_content(infile)
217    return MacroSectionDoc(content)
218
219def process_typedef(infile):
220    content = read_content(infile)
221    typedef = infile.readline()
222    typedef = re.sub(r';\n$', '', typedef)
223    typedef = re.sub(r'typedef ', '', typedef)
224    return TypedefDoc(typedef, content)
225
226def process_enum(infile):
227    members = []
228    enum_name = None
229    content = read_content(infile)
230    while True:
231        line = infile.readline()
232        if not line:
233            break
234        elif re.match(r'\s*/\*\*\n', line):
235            member_content = read_content(infile)
236            line = infile.readline()
237            items = line.split()
238            member_name = items[0].rstrip(',')
239            if len(items) >= 3:
240                member_content.insert(0, '(``{}``) '\
241                                      .format(' '.join(items[2:]).rstrip(',')))
242            members.append((member_name, member_content))
243        elif line.startswith('}'):
244            enum_name = line.rstrip().split()[1]
245            enum_name = re.sub(r';$', '', enum_name)
246            break
247    return EnumDoc(enum_name, content, members)
248
249def process_struct(infile):
250    members = []
251    struct_name = None
252    content = read_content(infile)
253    while True:
254        line = infile.readline()
255        if not line:
256            break
257        elif re.match(r'\s*/\*\*\n', line):
258            member_content = read_content(infile)
259            line = infile.readline()
260            member_name = line.rstrip().rstrip(';')
261            members.append((member_name, member_content))
262        elif line.startswith('}') or\
263                (line.startswith('typedef ') and line.endswith(';\n')):
264            if line.startswith('}'):
265                index = 1
266            else:
267                index = 3
268            struct_name = line.rstrip().split()[index]
269            struct_name = re.sub(r';$', '', struct_name)
270            break
271    return StructDoc(struct_name, content, members, 'member')
272
273def process_function(domain, infile):
274    content = read_content(infile)
275    func_proto = []
276    while True:
277        line = infile.readline()
278        if not line:
279            break
280        elif line == '\n':
281            break
282        else:
283            func_proto.append(line)
284    func_proto = ''.join(func_proto)
285    func_proto = re.sub(r';\n$', '', func_proto)
286    func_proto = re.sub(r'\s+', ' ', func_proto)
287    func_proto = re.sub(r'NGHTTP2_EXTERN ', '', func_proto)
288    func_proto = re.sub(r'typedef ', '', func_proto)
289    filename = os.path.basename(infile.name)
290    return FunctionDoc(func_proto, content, domain, filename)
291
292def read_content(infile):
293    content = []
294    while True:
295        line = infile.readline()
296        if not line:
297            break
298        if re.match(r'\s*\*/\n', line):
299            break
300        else:
301            content.append(transform_content(line.rstrip()))
302    return content
303
304def arg_repl(matchobj):
305    return '*{}*'.format(matchobj.group(1).replace('*', '\\*'))
306
307def transform_content(content):
308    content = re.sub(r'^\s+\* ?', '', content)
309    content = re.sub(r'\|([^\s|]+)\|', arg_repl, content)
310    content = re.sub(r':enum:', ':macro:', content)
311    return content
312
313if __name__ == '__main__':
314    parser = argparse.ArgumentParser(description="Generate API reference")
315    parser.add_argument('--title', default='API Reference',
316                        help='title of index page')
317    parser.add_argument('index', type=argparse.FileType('w'),
318                        help='index output file')
319    parser.add_argument('macros', type=argparse.FileType('w'),
320                        help='macros section output file.  The filename should be macros.rst')
321    parser.add_argument('enums', type=argparse.FileType('w'),
322                        help='enums section output file.  The filename should be enums.rst')
323    parser.add_argument('types', type=argparse.FileType('w'),
324                        help='types section output file.  The filename should be types.rst')
325    parser.add_argument('funcsdir',
326                        help='functions doc output dir')
327    parser.add_argument('files', nargs='+', type=argparse.FileType('r'),
328                        help='source file')
329    args = parser.parse_args()
330    macros = []
331    enums = []
332    types = []
333    funcs = []
334    for infile in args.files:
335        m, e, t, f = make_api_ref(infile)
336        macros.extend(m)
337        enums.extend(e)
338        types.extend(t)
339        funcs.extend(f)
340    funcs.sort(key=lambda x: x.funcname)
341    output(
342        args.title,
343        args.index, args.macros, args.enums, args.types, args.funcsdir,
344        macros, enums, types, funcs)
345