1#!/usr/bin/env python
2#
3# aria2 - The high speed download utility
4#
5# Copyright (C) 2013 Tatsuhiro Tsujikawa
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20#
21# In addition, as a special exception, the copyright holders give
22# permission to link the code of portions of this program with the
23# OpenSSL library under certain conditions as described in each
24# individual source file, and distribute linked combinations
25# including the two.
26# You must obey the GNU General Public License in all respects
27# for all of the code used other than OpenSSL.  If you modify
28# file(s) with this exception, you may extend this exception to your
29# version of the file(s), but you are not obligated to do so.  If you
30# do not wish to do so, delete this exception statement from your
31# version.  If you delete this exception statement from all source
32# files in the program, then also delete it here.
33#
34# Generates API reference from C++ source code.
35from __future__ import print_function
36import re, sys, argparse
37
38class FunctionDoc:
39    def __init__(self, name, content, domain):
40        self.name = name
41        self.content = content
42        self.domain = domain
43
44    def write(self, out):
45        print('''.. {}:: {}'''.format(self.domain, self.name))
46        print()
47        for line in self.content:
48            print('    {}'.format(line))
49
50class TypedefDoc:
51    def __init__(self, name, content):
52        self.name = name
53        self.content = content
54
55    def write(self, out):
56        print('''.. type:: {}'''.format(self.name))
57        print()
58        for line in self.content:
59            print('    {}'.format(line))
60
61class StructDoc:
62    def __init__(self, name, content, domain, members, member_domain):
63        self.name = name
64        self.content = content
65        self.domain = domain
66        self.members = members
67        self.member_domain = member_domain
68
69    def write(self, out):
70        if self.name:
71            print('''.. {}:: {}'''.format(self.domain, self.name))
72            print()
73            for line in self.content:
74                print('    {}'.format(line))
75            print()
76            for name, content in self.members:
77                name = name.strip()
78                # For function (e.g., int foo())
79                m = re.match(r'(.+)\s+([^ ]+\(.*)', name)
80                if not m:
81                    # For variable (e.g., bool a)
82                    m = re.match(r'(.+)\s+([^ ]+)', name)
83                if m:
84                    print('''    .. {}:: {} {}::{}'''.format(
85                        'function' if name.endswith(')') else self.member_domain,
86                        m.group(1),
87                        self.name,
88                        m.group(2)))
89                else:
90                    if name.endswith(')'):
91                        # For function, without return type, like
92                        # constructor
93                        print('''    .. {}:: {}::{}'''.format(
94                            'function' if name.endswith(')') else self.member_domain,
95                            self.name, name))
96                    else:
97                        # enum
98                        print('''    .. {}:: {}'''.format(
99                            'function' if name.endswith(')') else self.member_domain,
100                            name))
101                print()
102                for line in content:
103                    print('''        {}'''.format(line))
104            print()
105
106class MacroDoc:
107    def __init__(self, name, content):
108        self.name = name
109        self.content = content
110
111    def write(self, out):
112        print('''.. macro:: {}'''.format(self.name))
113        print()
114        for line in self.content:
115            print('    {}'.format(line))
116
117def make_api_ref(infiles):
118    macros = []
119    enums = []
120    types = []
121    functions = []
122    for infile in infiles:
123        while True:
124            line = infile.readline()
125            if not line:
126                break
127            elif line == '/**\n':
128                line = infile.readline()
129                doctype = line.split()[1]
130                if doctype == '@function':
131                    functions.append(process_function('function', infile))
132                if doctype == '@functypedef':
133                    types.append(process_function('type', infile))
134                elif doctype == '@typedef':
135                    types.append(process_typedef(infile))
136                elif doctype in ['@class', '@struct', '@union']:
137                    types.append(process_struct(infile))
138                elif doctype == '@enum':
139                    enums.append(process_enum(infile))
140                elif doctype == '@macro':
141                    macros.append(process_macro(infile))
142    alldocs = [('Macros', macros),
143               ('Enums', enums),
144               ('Types (classes, structs, unions and typedefs)', types),
145               ('Functions', functions)]
146    for title, docs in alldocs:
147        if not docs:
148            continue
149        print(title)
150        print('-'*len(title))
151        for doc in docs:
152            doc.write(sys.stdout)
153            print()
154        print()
155
156def process_macro(infile):
157    content = read_content(infile)
158    line = infile.readline()
159    macro_name = line.split()[1]
160    return MacroDoc(macro_name, content)
161
162def process_enum(infile):
163    members = []
164    enum_name = None
165    content = read_content(infile)
166    while True:
167        line = infile.readline()
168        if not line:
169            break
170        elif re.match(r'\s*/\*\*\n', line):
171            member_content = read_content(infile)
172            line = infile.readline()
173            items = line.split()
174            member_name = items[0].rstrip(',')
175            if len(items) >= 3:
176                member_content.insert(0, '(``{}``) '\
177                                          .format(items[2].rstrip(',')))
178            members.append((member_name, member_content))
179        elif line.startswith('}'):
180            if not enum_name:
181                enum_name = line.rstrip().split()[1]
182            enum_name = re.sub(r';$', '', enum_name)
183            break
184        elif not enum_name:
185            m = re.match(r'^\s*enum\s+([\S]+)\s*{\s*', line)
186            if m:
187                enum_name = m.group(1)
188    return StructDoc(enum_name, content, 'type', members, 'c:macro')
189
190def process_struct(infile):
191    members = []
192    domain = 'type'
193    struct_name = None
194    content = read_content(infile)
195    while True:
196        line = infile.readline()
197        if not line:
198            break
199        elif re.match(r'\s*/\*\*\n', line):
200            member_content = read_content(infile)
201            line = infile.readline()
202            member_name = line.rstrip().rstrip(';')
203            member_name = re.sub(r'\)\s*=\s*0', ')', member_name)
204            member_name = re.sub(r' virtual ', '', member_name)
205            members.append((member_name, member_content))
206        elif line.startswith('}') or\
207                (line.startswith('typedef ') and line.endswith(';\n')):
208            if not struct_name:
209                if line.startswith('}'):
210                    index = 1
211                else:
212                    index = 3
213                struct_name = line.rstrip().split()[index]
214            struct_name = re.sub(r';$', '', struct_name)
215            break
216        elif not struct_name:
217            m = re.match(r'^\s*(struct|class)\s+([\S]+)\s*(?:{|;)', line)
218            if m:
219                domain = m.group(1)
220                if domain == 'struct':
221                    domain = 'type'
222                struct_name = m.group(2)
223                if line.endswith(';\n'):
224                    break
225    return StructDoc(struct_name, content, domain, members, 'member')
226
227def process_function(domain, infile):
228    content = read_content(infile)
229    func_proto = []
230    while True:
231        line = infile.readline()
232        if not line:
233            break
234        elif line == '\n':
235            break
236        else:
237            func_proto.append(line)
238    func_proto = ''.join(func_proto)
239    func_proto = re.sub(r';\n$', '', func_proto)
240    func_proto = re.sub(r'\s+', ' ', func_proto)
241    func_proto = re.sub(r'typedef ', '', func_proto)
242    return FunctionDoc(func_proto, content, domain)
243
244def process_typedef(infile):
245    content = read_content(infile)
246    lines = []
247    while True:
248        line = infile.readline()
249        if not line:
250            break
251        elif line == '\n':
252            break
253        else:
254            lines.append(line)
255    typedef = ''.join(lines)
256    typedef = re.sub(r';\n$', '', typedef)
257    typedef = re.sub(r'\s+', ' ', typedef)
258    typedef = re.sub(r'typedef ', '', typedef)
259    return TypedefDoc(typedef.split()[-1], content)
260
261def read_content(infile):
262    content = []
263    while True:
264        line = infile.readline()
265        if not line:
266            break
267        if re.match(r'\s*\*/\n', line):
268            break
269        else:
270            content.append(transform_content(line.rstrip()))
271    return content
272
273def arg_repl(matchobj):
274    return '*{}*'.format(matchobj.group(1).replace('*', '\\*'))
275
276def transform_content(content):
277    content = re.sub(r'^\s+\* ?', '', content)
278    content = re.sub(r'\|([^\s|]+)\|', arg_repl, content)
279    content = re.sub(r':enum:', ':macro:', content)
280    return content
281
282if __name__ == '__main__':
283    parser = argparse.ArgumentParser(description="Generate API reference")
284    parser.add_argument('--header', type=argparse.FileType('rb', 0),
285                        help='header inserted at the top of the page')
286    parser.add_argument('files', nargs='+', type=argparse.FileType('rb', 0),
287                        help='source file')
288    args = parser.parse_args()
289    if args.header:
290        print(args.header.read())
291    for infile in args.files:
292        make_api_ref(args.files)
293