#!/usr/bin/env python3 # -*- coding: utf-8 -*- # nghttp2 - HTTP/2 C Library # # Copyright (c) 2020 nghttp2 contributors # Copyright (c) 2020 ngtcp2 contributors # Copyright (c) 2012 Tatsuhiro Tsujikawa # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Generates API reference from C source code. import re, sys, argparse, os.path class FunctionDoc: def __init__(self, name, content, domain, filename): self.name = name self.content = content self.domain = domain if self.domain == 'function': self.funcname = re.search(r'(nghttp2_[^ )]+)\(', self.name).group(1) self.filename = filename def write(self, out): out.write('.. {}:: {}\n'.format(self.domain, self.name)) out.write('\n') for line in self.content: out.write(' {}\n'.format(line)) class StructDoc: def __init__(self, name, content, members, member_domain): self.name = name self.content = content self.members = members self.member_domain = member_domain def write(self, out): if self.name: out.write('.. type:: {}\n'.format(self.name)) out.write('\n') for line in self.content: out.write(' {}\n'.format(line)) out.write('\n') for name, content in self.members: out.write(' .. {}:: {}\n'.format(self.member_domain, name)) out.write('\n') for line in content: out.write(' {}\n'.format(line)) out.write('\n') class EnumDoc: def __init__(self, name, content, members): self.name = name self.content = content self.members = members def write(self, out): if self.name: out.write('.. type:: {}\n'.format(self.name)) out.write('\n') for line in self.content: out.write(' {}\n'.format(line)) out.write('\n') for name, content in self.members: out.write(' .. enum:: {}\n'.format(name)) out.write('\n') for line in content: out.write(' {}\n'.format(line)) out.write('\n') class MacroDoc: def __init__(self, name, content): self.name = name self.content = content def write(self, out): out.write('''.. macro:: {}\n'''.format(self.name)) out.write('\n') for line in self.content: out.write(' {}\n'.format(line)) class MacroSectionDoc: def __init__(self, content): self.content = content def write(self, out): out.write('\n') c = ' '.join(self.content).strip() out.write(c) out.write('\n') out.write('-' * len(c)) out.write('\n\n') class TypedefDoc: def __init__(self, name, content): self.name = name self.content = content def write(self, out): out.write('''.. type:: {}\n'''.format(self.name)) out.write('\n') for line in self.content: out.write(' {}\n'.format(line)) def make_api_ref(infile): macros = [] enums = [] types = [] functions = [] while True: line = infile.readline() if not line: break elif line == '/**\n': line = infile.readline() doctype = line.split()[1] if doctype == '@function': functions.append(process_function('function', infile)) elif doctype == '@functypedef': types.append(process_function('type', infile)) elif doctype == '@struct' or doctype == '@union': types.append(process_struct(infile)) elif doctype == '@enum': enums.append(process_enum(infile)) elif doctype == '@macro': macros.append(process_macro(infile)) elif doctype == '@macrosection': macros.append(process_macrosection(infile)) elif doctype == '@typedef': types.append(process_typedef(infile)) return macros, enums, types, functions def output( title, indexfile, macrosfile, enumsfile, typesfile, funcsdir, macros, enums, types, functions): indexfile.write(''' {title} {titledecoration} .. toctree:: :maxdepth: 1 {macros} {enums} {types} '''.format( title=title, titledecoration='='*len(title), macros=os.path.splitext(os.path.basename(macrosfile.name))[0], enums=os.path.splitext(os.path.basename(enumsfile.name))[0], types=os.path.splitext(os.path.basename(typesfile.name))[0], )) for doc in functions: indexfile.write(' {}\n'.format(doc.funcname)) macrosfile.write(''' Macros ====== ''') for doc in macros: doc.write(macrosfile) enumsfile.write(''' Enums ===== ''') for doc in enums: doc.write(enumsfile) typesfile.write(''' Types (structs, unions and typedefs) ==================================== ''') for doc in types: doc.write(typesfile) for doc in functions: with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f: f.write(''' {funcname} {secul} Synopsis -------- *#include * '''.format(funcname=doc.funcname, secul='='*len(doc.funcname), filename=doc.filename)) doc.write(f) def process_macro(infile): content = read_content(infile) line = infile.readline() macro_name = line.split()[1] return MacroDoc(macro_name, content) def process_macrosection(infile): content = read_content(infile) return MacroSectionDoc(content) def process_typedef(infile): content = read_content(infile) typedef = infile.readline() typedef = re.sub(r';\n$', '', typedef) typedef = re.sub(r'typedef ', '', typedef) return TypedefDoc(typedef, content) def process_enum(infile): members = [] enum_name = None content = read_content(infile) while True: line = infile.readline() if not line: break elif re.match(r'\s*/\*\*\n', line): member_content = read_content(infile) line = infile.readline() items = line.split() member_name = items[0].rstrip(',') if len(items) >= 3: member_content.insert(0, '(``{}``) '\ .format(' '.join(items[2:]).rstrip(','))) members.append((member_name, member_content)) elif line.startswith('}'): enum_name = line.rstrip().split()[1] enum_name = re.sub(r';$', '', enum_name) break return EnumDoc(enum_name, content, members) def process_struct(infile): members = [] struct_name = None content = read_content(infile) while True: line = infile.readline() if not line: break elif re.match(r'\s*/\*\*\n', line): member_content = read_content(infile) line = infile.readline() member_name = line.rstrip().rstrip(';') members.append((member_name, member_content)) elif line.startswith('}') or\ (line.startswith('typedef ') and line.endswith(';\n')): if line.startswith('}'): index = 1 else: index = 3 struct_name = line.rstrip().split()[index] struct_name = re.sub(r';$', '', struct_name) break return StructDoc(struct_name, content, members, 'member') def process_function(domain, infile): content = read_content(infile) func_proto = [] while True: line = infile.readline() if not line: break elif line == '\n': break else: func_proto.append(line) func_proto = ''.join(func_proto) func_proto = re.sub(r';\n$', '', func_proto) func_proto = re.sub(r'\s+', ' ', func_proto) func_proto = re.sub(r'NGHTTP2_EXTERN ', '', func_proto) func_proto = re.sub(r'typedef ', '', func_proto) filename = os.path.basename(infile.name) return FunctionDoc(func_proto, content, domain, filename) def read_content(infile): content = [] while True: line = infile.readline() if not line: break if re.match(r'\s*\*/\n', line): break else: content.append(transform_content(line.rstrip())) return content def arg_repl(matchobj): return '*{}*'.format(matchobj.group(1).replace('*', '\\*')) def transform_content(content): content = re.sub(r'^\s+\* ?', '', content) content = re.sub(r'\|([^\s|]+)\|', arg_repl, content) content = re.sub(r':enum:', ':macro:', content) return content if __name__ == '__main__': parser = argparse.ArgumentParser(description="Generate API reference") parser.add_argument('--title', default='API Reference', help='title of index page') parser.add_argument('index', type=argparse.FileType('w'), help='index output file') parser.add_argument('macros', type=argparse.FileType('w'), help='macros section output file. The filename should be macros.rst') parser.add_argument('enums', type=argparse.FileType('w'), help='enums section output file. The filename should be enums.rst') parser.add_argument('types', type=argparse.FileType('w'), help='types section output file. The filename should be types.rst') parser.add_argument('funcsdir', help='functions doc output dir') parser.add_argument('files', nargs='+', type=argparse.FileType('r'), help='source file') args = parser.parse_args() macros = [] enums = [] types = [] funcs = [] for infile in args.files: m, e, t, f = make_api_ref(infile) macros.extend(m) enums.extend(e) types.extend(t) funcs.extend(f) funcs.sort(key=lambda x: x.funcname) output( args.title, args.index, args.macros, args.enums, args.types, args.funcsdir, macros, enums, types, funcs)