#!/usr/bin/env python import getopt import os import re import sys import definitions import defsparser import docextract import override class Node: def __init__(self, name, interfaces=[]): self.name = name self.interfaces = interfaces self.subclasses = [] def add_child(self, node): self.subclasses.append(node) def build_object_tree(parser): # reorder objects so that parent classes come first ... objects = parser.objects[:] pos = 0 while pos < len(objects): parent = objects[pos].parent for i in range(pos+1, len(objects)): if objects[i].c_name == parent: objects.insert(i+1, objects[pos]) del objects[pos] break else: pos = pos + 1 root = Node(None) nodes = {None: root} for obj_def in objects: parent_name = obj_def.parent if parent_name == 'GObject': parent_name = None parent_node = nodes[parent_name] node = Node(obj_def.c_name, obj_def.implements) parent_node.add_child(node) nodes[node.name] = node if parser.interfaces: interfaces = Node('gobject.GInterface') root.add_child(interfaces) nodes[interfaces.name] = interfaces for obj_def in parser.interfaces: node = Node(obj_def.c_name) interfaces.add_child(node) nodes[node.name] = node if parser.boxes: boxed = Node('gobject.GBoxed') root.add_child(boxed) nodes[boxed.name] = boxed for obj_def in parser.boxes: node = Node(obj_def.c_name) boxed.add_child(node) nodes[node.name] = node if parser.pointers: pointers = Node('gobject.GPointer') root.add_child(pointers) nodes[pointers.name] = pointers for obj_def in parser.pointers: node = Node(obj_def.c_name) pointers.add_child(node) nodes[node.name] = node return root class DocWriter: def __init__(self): self._fp = None # parse the defs file self.parser = defsparser.DefsParser(()) self.overrides = override.Overrides() self.classmap = {} self.docs = {} def add_sourcedirs(self, source_dirs): self.docs = docextract.extract(source_dirs, self.docs) def add_tmpldirs(self, tmpl_dirs): self.docs = docextract.extract_tmpl(tmpl_dirs, self.docs) def add_docs(self, defs_file, overrides_file, module_name): '''parse information about a given defs file''' self.parser.filename = defs_file self.parser.startParsing(defs_file) if overrides_file: self.overrides.handle_file(overrides_file) for obj in (self.parser.objects + self.parser.interfaces + self.parser.boxes + self.parser.pointers): if not obj.c_name in self.classmap: self.classmap[obj.c_name] = '%s.%s' % ( module_name, obj.name) def pyname(self, name): return self.classmap.get(name, name) def _compare(self, obja, objb): return cmp(self.pyname(obja.c_name), self.pyname(objb.c_name)) def output_docs(self, output_prefix): files = {} # class hierarchy hierarchy = build_object_tree(self.parser) filename = self.create_filename('hierarchy', output_prefix) self._fp = open(filename, 'w') self.write_full_hierarchy(hierarchy) self._fp.close() obj_defs = (self.parser.objects + self.parser.interfaces + self.parser.boxes + self.parser.pointers) obj_defs.sort(self._compare) for obj_def in obj_defs: filename = self.create_filename(obj_def.c_name, output_prefix) self._fp = open(filename, 'w') if isinstance(obj_def, definitions.ObjectDef): self.output_object_docs(obj_def) elif isinstance(obj_def, definitions.InterfaceDef): self.output_interface_docs(obj_def) elif isinstance(obj_def, definitions.BoxedDef): self.output_boxed_docs(obj_def) elif isinstance(obj_def, definitions.PointerDef): self.output_boxed_docs(obj_def) self._fp.close() files[os.path.basename(filename)] = obj_def if not files: return output_filename = self.create_toc_filename(output_prefix) self._fp = open(output_filename, 'w') self.output_toc(files) self._fp.close() def output_object_docs(self, obj_def): self.write_class_header(obj_def.c_name) self.write_heading('Synopsis') self.write_synopsis(obj_def) self.close_section() # construct the inheritence hierarchy ... ancestry = [(obj_def.c_name, obj_def.implements)] try: parent = obj_def.parent while parent != None: if parent == 'GObject': ancestry.append(('GObject', [])) parent = None else: parent_def = self.parser.find_object(parent) ancestry.append((parent_def.c_name, parent_def.implements)) parent = parent_def.parent except ValueError: pass ancestry.reverse() self.write_heading('Ancestry') self.write_hierarchy(obj_def.c_name, ancestry) self.close_section() constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: self.write_heading('Constructor') self.write_constructor(constructor, self.docs.get(constructor.c_name, None)) self.close_section() methods = self.parser.find_methods(obj_def) methods = filter(lambda meth, self=self: not self.overrides.is_ignored(meth.c_name), methods) if methods: self.write_heading('Methods') for method in methods: self.write_method(method, self.docs.get(method.c_name, None)) self.close_section() self.write_class_footer(obj_def.c_name) def get_methods_for_object(self, obj_def): methods = [] for method in self.parser.find_methods(obj_def): if not self.overrides.is_ignored(method.c_name): methods.append(method) return methods def output_interface_docs(self, int_def): self.write_class_header(int_def.c_name) self.write_heading('Synopsis') self.write_synopsis(int_def) self.close_section() methods = self.get_methods_for_object(int_def) if methods: self.write_heading('Methods') for method in methods: self.write_method(method, self.docs.get(method.c_name, None)) self.close_section() self.write_class_footer(int_def.c_name) def output_boxed_docs(self, box_def): self.write_class_header(box_def.c_name) self.write_heading('Synopsis') self.write_synopsis(box_def) self.close_section() constructor = self.parser.find_constructor(box_def, self.overrides) if constructor: self.write_heading('Constructor') self.write_constructor(constructor, self.docs.get(constructor.c_name, None)) self.close_section() methods = self.get_methods_for_object(box_def) if methods: self.write_heading('Methods') for method in methods: self.write_method(method, self.docs.get(method.c_name, None)) self.close_section() self.write_class_footer(box_def.c_name) def output_toc(self, files): self._fp.write('TOC\n\n') for filename in sorted(files): obj_def = files[filename] self._fp.write(obj_def.c_name + ' - ' + filename + '\n') # override the following to create a more complex output format def create_filename(self, obj_name, output_prefix): '''Create output filename for this particular object''' return output_prefix + '-' + obj_name.lower() + '.txt' def create_toc_filename(self, output_prefix): return self.create_filename(self, 'docs', output_prefix) def write_full_hierarchy(self, hierarchy): def handle_node(node, indent=''): for child in node.subclasses: self._fp.write(indent + node.name) if node.interfaces: self._fp.write(' (implements ') self._fp.write(', '.join(node.interfaces)) self._fp.write(')\n') else: self._fp.write('\n') handle_node(child, indent + ' ') handle_node(hierarchy) def serialize_params(self, func_def): params = [] for param in func_def.params: params.append(param[1]) return ', '.join(params) # these need to handle default args ... def create_constructor_prototype(self, func_def): return '%s(%s)' % (func_def.is_constructor_of, self.serialize_params(func_def)) def create_function_prototype(self, func_def): return '%s(%s)' % (func_def.name, self.serialize_params(func_def)) def create_method_prototype(self, meth_def): return '%s.%s(%s)' % (meth_def.of_object, meth_def.name, self.serialize_params(meth_def)) def write_class_header(self, obj_name): self._fp.write('Class %s\n' % obj_name) self._fp.write('======%s\n\n' % ('=' * len(obj_name))) def write_class_footer(self, obj_name): pass def write_heading(self, text): self._fp.write('\n' + text + '\n' + ('-' * len(text)) + '\n') def close_section(self): pass def write_synopsis(self, obj_def): self._fp.write('class %s' % obj_def.c_name) if isinstance(obj_def, definitions.ObjectDef): bases = [] if obj_def.parent: bases.append(obj_def.parent) bases = bases = obj_def.implements if bases: self._fp.write('(%s)' % ', '.join(bases, )) self._fp.write(':\n') constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: prototype = self.create_constructor_prototype(constructor) self._fp.write(' def %s\n' % prototype) for method in self.get_methods_for_object(obj_def): prototype = self.create_method_prototype(method) self._fp.write(' def %s\n' % prototype) def write_hierarchy(self, obj_name, ancestry): indent = '' for name, interfaces in ancestry: self._fp.write(indent + '+-- ' + name) if interfaces: self._fp.write(' (implements ') self._fp.write(', '.join(interfaces)) self._fp.write(')\n') else: self._fp.write('\n') indent = indent + ' ' self._fp.write('\n') def write_constructor(self, func_def, func_doc): prototype = self.create_constructor_prototype(func_def) self._fp.write(prototype + '\n\n') for type, name, dflt, null in func_def.params: self.write_parameter(name, func_doc) self.write_return_value(func_def, func_doc) if func_doc and func_doc.description: self._fp.write(func_doc.description) self._fp.write('\n\n\n') def write_method(self, meth_def, func_doc): prototype = self.create_method_prototype(meth_def) self._fp.write(prototype + '\n\n') for type, name, dflt, null in meth_def.params: self.write_parameter(name, func_doc) self.write_return_value(meth_def, func_doc) if func_doc and func_doc.description: self._fp.write('\n') self._fp.write(func_doc.description) self._fp.write('\n\n') def write_parameter(self, param_name, func_doc): if func_doc: descr = func_doc.get_param_description(param_name) else: descr = 'a ' + type self._fp.write(' ' + param_name + ': ' + descr + '\n') def write_return_value(self, meth_def, func_doc): if meth_def.ret and meth_def.ret != 'none': if func_doc and func_doc.ret: descr = func_doc.ret else: descr = 'a ' + meth_def.ret self._fp.write(' Returns: ' + descr + '\n') CLASS_HEADER_TEMPLATE = """ %(name)s 3 %(miscinfo)s %(name)s """ VARIABLE_TEMPLATE = """ %(parameter)s : %(description)s """ DOCBOOK_HEADER = """ """ class DocbookDocWriter(DocWriter): def __init__(self): DocWriter.__init__(self) self._function_pat = re.compile(r'(\w+)\s*\(\)') self._parameter_pat = re.compile(r'\@(\w+)') self._constant_pat = re.compile(r'\%(-?\w+)') self._symbol_pat = re.compile(r'#([\w-]+)') self._transtable = ['-'] * 256 # make string -> reference translation func for digit in '0123456789': self._transtable[ord(digit)] = digit for letter in 'abcdefghijklmnopqrstuvwxyz': self._transtable[ord(letter)] = letter self._transtable[ord(letter.upper())] = letter self._transtable = ''.join(self._transtable) def create_filename(self, obj_name, output_prefix): '''Create output filename for this particular object''' stem = output_prefix + '-' + obj_name.lower() return stem + '.xml' def create_toc_filename(self, output_prefix): return self.create_filename('classes', output_prefix) def make_class_ref(self, obj_name): return 'class-' + obj_name.translate(self._transtable) def make_method_ref(self, meth_def): return 'method-%s--%s' % ( meth_def.of_object.translate(self._transtable), meth_def.name.translate(self._transtable)) def _format_function(self, match): info = self.parser.c_name.get(match.group(1), None) if info: if isinstance(info, defsparser.FunctionDef): return self._format_funcdef(info) if isinstance(info, defsparser.MethodDef): return self._format_method(info) # fall through through return '%s()' % (match.group(1), ) def _format_funcdef(self, info): if info.is_constructor_of is not None: # should have a link here return '%s()' % ( self.pyname(info.is_constructor_of), ) else: return '%s()' % (info.name, ) def _format_param(self, match): return '%s' % (match.group(1), ) def _format_const(self, match): return '%s' % (match.group(1), ) def _format_method(self, info): return ('' '%s.%s' '') % (self.make_method_ref(info), self.pyname(info.of_object), info.name) def _format_object(self, info): return ('' '%s' '') % (self.make_class_ref(info.c_name), self.pyname(info.c_name)) def _format_symbol(self, match): info = self.parser.c_name.get(match.group(1), None) if info: if isinstance(info, defsparser.FunctionDef): return self._format_funcdef(info) elif isinstance(info, defsparser.MethodDef): return self._format_method(info) elif isinstance(info, (defsparser.ObjectDef, defsparser.InterfaceDef, defsparser.BoxedDef, defsparser.PointerDef)): return self._format_object(info) # fall through through return '%s' % (match.group(1), ) def reformat_text(self, text, singleline=0): # replace special strings ... text = self._function_pat.sub(self._format_function, text) text = self._parameter_pat.sub(self._format_param, text) text = self._constant_pat.sub(self._format_const, text) text = self._symbol_pat.sub(self._format_symbol, text) # don't bother with expansion for single line text. if singleline: return text lines = text.strip().split('\n') for index in range(len(lines)): if lines[index].strip() == '': lines[index] = '\n' continue return '%s' % ('\n'.join(lines), ) # write out hierarchy def write_full_hierarchy(self, hierarchy): def handle_node(node, indent=''): if node.name: self._fp.write('%s%s' % (indent, self.make_class_ref(node.name), self.pyname(node.name))) if node.interfaces: self._fp.write(' (implements ') for i in range(len(node.interfaces)): self._fp.write('%s' % (self.make_class_ref(node.interfaces[i]), self.pyname(node.interfaces[i]))) if i != len(node.interfaces) - 1: self._fp.write(', ') self._fp.write(')\n') else: self._fp.write('\n') indent = indent + ' ' node.subclasses.sort(lambda a, b: cmp(self.pyname(a.name), self.pyname(b.name))) for child in node.subclasses: handle_node(child, indent) self._fp.write(DOCBOOK_HEADER) self._fp.write('') handle_node(hierarchy) self._fp.write('\n') # these need to handle default args ... def create_constructor_prototype(self, func_def): xml = ['\n'] xml.append(' __init__\n') for type, name, dflt, null in func_def.params: xml.append(' ') xml.append(name) xml.append('') if dflt: xml.append('') xml.append(dflt) xml.append('') xml.append('\n') if not func_def.params: xml.append(' ') xml.append(' ') return ''.join(xml) def create_function_prototype(self, func_def): xml = ['\n \n'] xml.append(' ') xml.append(func_def.name) xml.append('\n') for type, name, dflt, null in func_def.params: xml.append(' ') xml.append(name) xml.append('') if dflt: xml.append('') xml.append(dflt) xml.append('') xml.append('\n') if not func_def.params: xml.append(' \n ') return ''.join(xml) def create_method_prototype(self, meth_def, addlink=0): xml = ['\n'] xml.append(' ') if addlink: xml.append('' % self.make_method_ref(meth_def)) xml.append(self.pyname(meth_def.name)) if addlink: xml.append('') xml.append('\n') for type, name, dflt, null in meth_def.params: xml.append(' ') xml.append(name) xml.append('') if dflt: xml.append('') xml.append(dflt) xml.append('') xml.append('\n') if not meth_def.params: xml.append(' ') xml.append(' ') return ''.join(xml) def write_class_header(self, obj_name): self._fp.write(DOCBOOK_HEADER) self._fp.write(CLASS_HEADER_TEMPLATE % dict( entryid=self.make_class_ref(obj_name), name=self.pyname(obj_name), miscinfo="PyGTK Docs")) def write_class_footer(self, obj_name): self._fp.write('\n') def write_heading(self, text): self._fp.write(' \n') self._fp.write(' ' + text + '\n\n') def close_section(self): self._fp.write(' \n') def write_synopsis(self, obj_def): self._fp.write('\n') self._fp.write(' %s\n' % self.pyname(obj_def.c_name)) if isinstance(obj_def, definitions.ObjectDef): if obj_def.parent: self._fp.write(' %s' '\n' % (self.make_class_ref(obj_def.parent), self.pyname(obj_def.parent))) for base in obj_def.implements: self._fp.write(' %s' '\n' % (self.make_class_ref(base), self.pyname(base))) elif isinstance(obj_def, definitions.InterfaceDef): self._fp.write(' gobject.GInterface' '\n') elif isinstance(obj_def, definitions.BoxedDef): self._fp.write(' gobject.GBoxed' '\n') elif isinstance(obj_def, definitions.PointerDef): self._fp.write(' gobject.GPointer' '\n') constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: self._fp.write( '%s\n' % self.create_constructor_prototype(constructor)) for method in self.get_methods_for_object(obj_def): self._fp.write( '%s\n' % self.create_method_prototype(method, addlink=1)) self._fp.write('\n\n') def write_hierarchy(self, obj_name, ancestry): self._fp.write('') indent = '' for name, interfaces in ancestry: self._fp.write( '%s+-- %s' % (indent, self.make_class_ref(name), self.pyname(name))) if interfaces: self._fp.write(' (implements ') for i in range(len(interfaces)): self._fp.write('%s' % (self.make_class_ref(interfaces[i]), self.pyname(interfaces[i]))) if i != len(interfaces) - 1: self._fp.write(', ') self._fp.write(')\n') else: self._fp.write('\n') indent = indent + ' ' self._fp.write('\n\n') def write_params(self, params, ret, func_doc): if not params and (not ret or ret == 'none'): return self._fp.write(' \n') for type, name, dflt, null in params: if func_doc: descr = func_doc.get_param_description(name).strip() else: descr = 'a ' + type self._fp.write(VARIABLE_TEMPLATE % dict( parameter=name, description=self.reformat_text(descr, singleline=1))) if ret and ret != 'none': if func_doc and func_doc.ret: descr = func_doc.ret.strip() else: descr = 'a ' + ret self._fp.write(VARIABLE_TEMPLATE % dict( parameter='Returns', description=self.reformat_text(descr, singleline=1))) self._fp.write(' \n') def write_constructor(self, func_def, func_doc): prototype = self.create_constructor_prototype(func_def) self._fp.write('%s\n' % prototype) self.write_params(func_def.params, func_def.ret, func_doc) if func_doc and func_doc.description: self._fp.write(self.reformat_text(func_doc.description)) self._fp.write('\n\n\n') def write_method(self, meth_def, func_doc): self._fp.write(' \n' % ( self.make_method_ref(meth_def), )) self._fp.write(' %s.%s\n\n' % ( self.pyname(meth_def.of_object), meth_def.name)) prototype = self.create_method_prototype(meth_def) self._fp.write('%s\n' % prototype) self.write_params(meth_def.params, meth_def.ret, func_doc) if func_doc and func_doc.description: self._fp.write(self.reformat_text(func_doc.description)) self._fp.write(' \n\n\n') def output_toc(self, files, fp=sys.stdout): self._fp.write(DOCBOOK_HEADER) #self._fp.write('\n') #self._fp.write(' Class Documentation\n') #for filename, obj_def in files: # self._fp.write('&' + # obj_def.c_name.translate(self._transtable) + ';\n') #self._fp.write('\n') self._fp.write('\n') self._fp.write(' Class Reference\n') for filename in sorted(files): self._fp.write(' \n' % filename) self._fp.write('\n') def main(args): try: opts, args = getopt.getopt(args[1:], "d:s:o:", ["defs-file=", "override=", "source-dir=", "output-prefix="]) except getopt.error, e: sys.stderr.write('docgen.py: %s\n' % e) sys.stderr.write( 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n') return 1 defs_file = None overrides_file = None source_dirs = [] output_prefix = 'docs' for opt, arg in opts: if opt in ('-d', '--defs-file'): defs_file = arg if opt in ('--override', ): overrides_file = arg elif opt in ('-s', '--source-dir'): source_dirs.append(arg) elif opt in ('-o', '--output-prefix'): output_prefix = arg if len(args) != 0 or not defs_file: sys.stderr.write( 'usage: docgen.py -d file.defs [-s /src/dir] [-o output-prefix]\n') return 1 d = DocbookDocWriter() d.add_sourcedirs(source_dirs) d.add_docs(defs_file, overrides_file, 'gio') d.output_docs(output_prefix) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))