#!/usr/bin/env python # -*- Mode: Python; py-indent-offset: 4 -*- import sys, os, string, re, getopt import defsparser import definitions import override import docextract 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_node = nodes[obj_def.parent] 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): # 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: if not self.classmap.has_key(obj.c_name): self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) for obj in self.parser.interfaces: if not self.classmap.has_key(obj.c_name): self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) for obj in self.parser.boxes: if not self.classmap.has_key(obj.c_name): self.classmap[obj.c_name] = '%s.%s' % (module_name, obj.name) for obj in self.parser.pointers: if not self.classmap.has_key(obj.c_name): 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) fp = open(filename, 'w') self.write_full_hierarchy(hierarchy, fp) 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) fp = open(filename, 'w') if isinstance(obj_def, definitions.ObjectDef): self.output_object_docs(obj_def, fp) elif isinstance(obj_def, definitions.InterfaceDef): self.output_interface_docs(obj_def, fp) elif isinstance(obj_def, definitions.BoxedDef): self.output_boxed_docs(obj_def, fp) elif isinstance(obj_def, definitions.PointerDef): self.output_boxed_docs(obj_def, fp) fp.close() files.append((os.path.basename(filename), obj_def)) if files: filename = self.create_toc_filename(output_prefix) fp = open(filename, 'w') self.output_toc(files, fp) fp.close() def output_object_docs(self, obj_def, fp=sys.stdout): self.write_class_header(obj_def.c_name, fp) self.write_heading('Synopsis', fp) self.write_synopsis(obj_def, fp) self.close_section(fp) # 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', fp) self.write_hierarchy(obj_def.c_name, ancestry, fp) self.close_section(fp) constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: self.write_heading('Constructor', fp) self.write_constructor(constructor, self.docs.get(constructor.c_name, None), fp) self.close_section(fp) 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', fp) for method in methods: self.write_method(method, self.docs.get(method.c_name, None), fp) self.close_section(fp) self.write_class_footer(obj_def.c_name, fp) def output_interface_docs(self, int_def, fp=sys.stdout): self.write_class_header(int_def.c_name, fp) self.write_heading('Synopsis', fp) self.write_synopsis(int_def, fp) self.close_section(fp) methods = self.parser.find_methods(int_def) methods = filter(lambda meth, self=self: not self.overrides.is_ignored(meth.c_name), methods) if methods: self.write_heading('Methods', fp) for method in methods: self.write_method(method, self.docs.get(method.c_name, None), fp) self.close_section(fp) self.write_class_footer(int_def.c_name, fp) def output_boxed_docs(self, box_def, fp=sys.stdout): self.write_class_header(box_def.c_name, fp) self.write_heading('Synopsis', fp) self.write_synopsis(box_def, fp) self.close_section(fp) constructor = self.parser.find_constructor(box_def, self.overrides) if constructor: self.write_heading('Constructor', fp) self.write_constructor(constructor, self.docs.get(constructor.c_name, None), fp) self.close_section(fp) methods = self.parser.find_methods(box_def) methods = filter(lambda meth, self=self: not self.overrides.is_ignored(meth.c_name), methods) if methods: self.write_heading('Methods', fp) for method in methods: self.write_method(method, self.docs.get(method.c_name, None), fp) self.close_section(fp) self.write_class_footer(box_def.c_name, fp) def output_toc(self, files, fp=sys.stdout): fp.write('TOC\n\n') for filename, obj_def in files: 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 + '-' + string.lower(obj_name) + '.txt' def create_toc_filename(self, output_prefix): return self.create_filename(self, 'docs', output_prefix) def write_full_hierarchy(self, hierarchy, fp): def handle_node(node, fp, indent=''): for child in node.subclasses: fp.write(indent + node.name) if node.interfaces: fp.write(' (implements ') fp.write(string.join(node.interfaces, ', ')) fp.write(')\n') else: fp.write('\n') handle_node(child, fp, indent + ' ') handle_node(hierarchy, fp) # these need to handle default args ... def create_constructor_prototype(self, func_def): return func_def.is_constructor_of + '(' + \ string.join(map(lambda x: x[1], func_def.params), ', ') + \ ')' def create_function_prototype(self, func_def): return func_def.name + '(' + \ string.join(map(lambda x: x[1], func_def.params), ', ') + \ ')' def create_method_prototype(self, meth_def): return meth_def.of_object + '.' + \ meth_def.name + '(' + \ string.join(map(lambda x: x[1], meth_def.params), ', ') + \ ')' def write_class_header(self, obj_name, fp): fp.write('Class %s\n' % obj_name) fp.write('======%s\n\n' % ('=' * len(obj_name))) def write_class_footer(self, obj_name, fp): pass def write_heading(self, text, fp): fp.write('\n' + text + '\n' + ('-' * len(text)) + '\n') def close_section(self, fp): pass def write_synopsis(self, obj_def, fp): 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: fp.write('(%s)' % string.join(bases, ', ')) fp.write(':\n') constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: prototype = self.create_constructor_prototype(constructor) fp.write(' def %s\n' % prototype) methods = self.parser.find_methods(obj_def) methods = filter(lambda meth, self=self: not self.overrides.is_ignored(meth.c_name), methods) for meth in methods: prototype = self.create_method_prototype(meth) fp.write(' def %s\n' % prototype) def write_hierarchy(self, obj_name, ancestry, fp): indent = '' for name, interfaces in ancestry: fp.write(indent + '+-- ' + name) if interfaces: fp.write(' (implements ') fp.write(string.join(interfaces, ', ')) fp.write(')\n') else: fp.write('\n') indent = indent + ' ' fp.write('\n') def write_constructor(self, func_def, func_doc, fp): prototype = self.create_constructor_prototype(func_def) fp.write(prototype + '\n\n') for type, name, dflt, null in func_def.params: if func_doc: descr = func_doc.get_param_description(name) else: descr = 'a ' + type fp.write(' ' + name + ': ' + descr + '\n') if func_def.ret and func_def.ret != 'none': if func_doc and func_doc.ret: descr = func_doc.ret else: descr = 'a ' + func_def.ret fp.write(' Returns: ' + descr + '\n') if func_doc and func_doc.description: fp.write(func_doc.description) fp.write('\n\n\n') def write_method(self, meth_def, func_doc, fp): prototype = self.create_method_prototype(meth_def) fp.write(prototype + '\n\n') for type, name, dflt, null in meth_def.params: if func_doc: descr = func_doc.get_param_description(name) else: descr = 'a ' + type fp.write(' ' + name + ': ' + descr + '\n') 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 fp.write(' Returns: ' + descr + '\n') if func_doc and func_doc.description: fp.write('\n') fp.write(func_doc.description) fp.write('\n\n') class DocbookDocWriter(DocWriter): def __init__(self, use_xml=0): DocWriter.__init__(self) self.use_xml = use_xml def create_filename(self, obj_name, output_prefix): '''Create output filename for this particular object''' stem = output_prefix + '-' + string.lower(obj_name) if self.use_xml: return stem + '.xml' else: return stem + '.sgml' def create_toc_filename(self, output_prefix): if self.use_xml: return self.create_filename('classes', output_prefix) else: return self.create_filename('docs', output_prefix) # make string -> reference translation func __transtable = [ '-' ] * 256 for digit in '0123456789': __transtable[ord(digit)] = digit for letter in 'abcdefghijklmnopqrstuvwxyz': __transtable[ord(letter)] = letter __transtable[ord(string.upper(letter))] = letter __transtable = string.join(__transtable, '') def make_class_ref(self, obj_name): return 'class-' + string.translate(obj_name, self.__transtable) def make_method_ref(self, meth_def): return 'method-' + string.translate(meth_def.of_object, self.__transtable) + \ '--' + string.translate(meth_def.name, self.__transtable) __function_pat = re.compile(r'(\w+)\s*\(\)') def __format_function(self, match): info = self.parser.c_name.get(match.group(1), None) if info: if isinstance(info, defsparser.FunctionDef): if info.is_constructor_of is not None: # should have a link here return '%s()' % \ self.pyname(info.is_constructor_of) else: return '' + info.name + '()' if isinstance(info, defsparser.MethodDef): return '' + self.pyname(info.of_object) + '.' + \ info.name + '()' # fall through through return '' + match.group(1) + '()' __parameter_pat = re.compile(r'\@(\w+)') def __format_param(self, match): return '' + match.group(1) + '' __constant_pat = re.compile(r'\%(-?\w+)') def __format_const(self, match): return '' + match.group(1) + '' __symbol_pat = re.compile(r'#([\w-]+)') def __format_symbol(self, match): info = self.parser.c_name.get(match.group(1), None) if info: if isinstance(info, defsparser.FunctionDef): if info.is_constructor_of is not None: # should have a link here return '' + self.pyname(info.is_constructor_of) + \ '' else: return '' + info.name + '' if isinstance(info, defsparser.MethodDef): return '' + self.pyname(info.of_object) + '.' + \ info.name + '' if isinstance(info, defsparser.ObjectDef) or \ isinstance(info, defsparser.InterfaceDef) or \ isinstance(info, defsparser.BoxedDef) or \ isinstance(info, defsparser.PointerDef): return '' + self.pyname(info.c_name) + \ '' # fall through through return '' + 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 = string.split(string.strip(text), '\n') for index in range(len(lines)): if string.strip(lines[index]) == '': lines[index] = '\n' continue lines.insert(0, '') lines.append('') return string.join(lines, '\n') # write out hierarchy def write_full_hierarchy(self, hierarchy, fp): def handle_node(node, fp, indent=''): if node.name: fp.write('%s%s' % (indent, self.make_class_ref(node.name), self.pyname(node.name))) if node.interfaces: fp.write(' (implements ') for i in range(len(node.interfaces)): fp.write('%s' % (self.make_class_ref(node.interfaces[i]), self.pyname(node.interfaces[i]))) if i != len(node.interfaces) - 1: fp.write(', ') fp.write(')\n') else: 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, fp, indent) if self.use_xml: fp.write('\n') fp.write('\n') fp.write('') handle_node(hierarchy, fp) fp.write('\n') # these need to handle default args ... def create_constructor_prototype(self, func_def): sgml = [ '\n'] sgml.append(' __init__\n') for type, name, dflt, null in func_def.params: sgml.append(' ') sgml.append(name) sgml.append('') if dflt: sgml.append('') sgml.append(dflt) sgml.append('') sgml.append('\n') if not func_def.params: sgml.append(' ') sgml.append(' ') return string.join(sgml, '') def create_function_prototype(self, func_def): sgml = [ '\n \n'] sgml.append(' ') sgml.append(func_def.name) sgml.append('\n') for type, name, dflt, null in func_def.params: sgml.append(' ') sgml.append(name) sgml.append('') if dflt: sgml.append('') sgml.append(dflt) sgml.append('') sgml.append('\n') if not func_def.params: sgml.append(' \n ') return string.join(sgml, '') def create_method_prototype(self, meth_def, addlink=0): sgml = [ '\n'] sgml.append(' ') if addlink: sgml.append('' % self.make_method_ref(meth_def)) sgml.append(self.pyname(meth_def.name)) if addlink: sgml.append('') sgml.append('\n') for type, name, dflt, null in meth_def.params: sgml.append(' ') sgml.append(name) sgml.append('') if dflt: sgml.append('') sgml.append(dflt) sgml.append('') sgml.append('\n') if not meth_def.params: sgml.append(' ') sgml.append(' ') return string.join(sgml, '') def write_class_header(self, obj_name, fp): if self.use_xml: fp.write('\n') fp.write('\n') fp.write('\n') fp.write(' \n') fp.write(' %s\n' % self.pyname(obj_name)) fp.write(' 3\n') fp.write(' PyGTK Docs\n') fp.write(' \n\n') fp.write(' \n') fp.write(' %s\n' % self.pyname(obj_name)) fp.write(' \n\n') def write_class_footer(self, obj_name, fp): fp.write('\n') def write_heading(self, text, fp): fp.write(' \n') fp.write(' ' + text + '\n\n') def close_section(self, fp): fp.write(' \n') def write_synopsis(self, obj_def, fp): fp.write('\n') fp.write(' %s\n' % self.pyname(obj_def.c_name)) if isinstance(obj_def, definitions.ObjectDef): if obj_def.parent: fp.write(' %s' '\n' % (self.make_class_ref(obj_def.parent), self.pyname(obj_def.parent))) for base in obj_def.implements: fp.write(' %s' '\n' % (self.make_class_ref(base), self.pyname(base))) elif isinstance(obj_def, definitions.InterfaceDef): fp.write(' gobject.GInterface' '\n') elif isinstance(obj_def, definitions.BoxedDef): fp.write(' gobject.GBoxed' '\n') elif isinstance(obj_def, definitions.PointerDef): fp.write(' gobject.GPointer' '\n') constructor = self.parser.find_constructor(obj_def, self.overrides) if constructor: fp.write('%s\n' % self.create_constructor_prototype(constructor)) methods = self.parser.find_methods(obj_def) methods = filter(lambda meth, self=self: not self.overrides.is_ignored(meth.c_name), methods) for meth in methods: fp.write('%s\n' % self.create_method_prototype(meth, addlink=1)) fp.write('\n\n') def write_hierarchy(self, obj_name, ancestry, fp): fp.write('') indent = '' for name, interfaces in ancestry: fp.write(indent + '+-- '+ self.pyname(name) + '') if interfaces: fp.write(' (implements ') for i in range(len(interfaces)): fp.write('%s' % (self.make_class_ref(interfaces[i]), self.pyname(interfaces[i]))) if i != len(interfaces) - 1: fp.write(', ') fp.write(')\n') else: fp.write('\n') indent = indent + ' ' fp.write('\n\n') def write_params(self, params, ret, func_doc, fp): if not params and (not ret or ret == 'none'): return fp.write(' \n') for type, name, dflt, null in params: if func_doc: descr = string.strip(func_doc.get_param_description(name)) else: descr = 'a ' + type fp.write(' \n') fp.write(' %s :\n' % name) fp.write(' %s\n' % self.reformat_text(descr, singleline=1)) fp.write(' \n') if ret and ret != 'none': if func_doc and func_doc.ret: descr = string.strip(func_doc.ret) else: descr = 'a ' + ret fp.write(' \n') fp.write(' Returns :\n') fp.write(' %s\n' % self.reformat_text(descr, singleline=1)) fp.write(' \n') fp.write(' \n') def write_constructor(self, func_def, func_doc, fp): prototype = self.create_constructor_prototype(func_def) fp.write('%s\n' % prototype) self.write_params(func_def.params, func_def.ret, func_doc, fp) if func_doc and func_doc.description: fp.write(self.reformat_text(func_doc.description)) fp.write('\n\n\n') def write_method(self, meth_def, func_doc, fp): fp.write(' \n') fp.write(' ' + self.pyname(meth_def.of_object) + '.' + meth_def.name + '\n\n') prototype = self.create_method_prototype(meth_def) fp.write('%s\n' % prototype) self.write_params(meth_def.params, meth_def.ret, func_doc, fp) if func_doc and func_doc.description: fp.write(self.reformat_text(func_doc.description)) fp.write(' \n\n\n') def output_toc(self, files, fp=sys.stdout): if self.use_xml: fp.write('\n') fp.write('\n') #for filename, obj_def in files: # fp.write(' \n') #fp.write(']>\n\n') #fp.write('\n') #fp.write(' Class Documentation\n') #for filename, obj_def in files: # fp.write('&' + string.translate(obj_def.c_name, # self.__transtable) + ';\n') #fp.write('\n') fp.write('\n') fp.write(' Class Reference\n') for filename, obj_def in files: fp.write(' \n' % filename) fp.write('\n') else: fp.write('\n') fp.write(']>\n\n') fp.write('\n\n') fp.write(' \n') fp.write(' PyGTK Docs\n') fp.write(' \n') fp.write(' \n') fp.write(' James\n') fp.write(' Henstridge\n') fp.write(' \n') fp.write(' \n') fp.write(' \n\n') fp.write(' \n') fp.write(' Class Hierarchy\n') fp.write(' Not done yet\n') fp.write(' \n\n') fp.write(' \n') fp.write(' Class Documentation\n') for filename, obj_def in files: fp.write('&' + string.translate(obj_def.c_name, self.__transtable) + ';\n') fp.write(' \n') fp.write('\n') if __name__ == '__main__': try: opts, args = getopt.getopt(sys.argv[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') sys.exit(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') sys.exit(1) d = DocbookDocWriter() d.add_sourcedirs(source_dirs) d.add_docs(defs_file, overrides_file, 'gtk') d.output_docs(output_prefix)