#!/usr/bin/env python # Simple Python extension module doc generator. # @(#) $Id: genextdoc.py,v 1.5 2004/10/07 15:32:31 yeti Exp $ # Written by Yeti # This program is in the public domain. import re, sys, types, inspect from cgi import escape as q args = sys.argv args.pop(0) if not args: print 'Usage: genextdoc.py [--selfcontained] module_name' sys.exit(0) selfcontained = False if args[0].startswith('-') and 'selfcontained'.startswith(args[0].strip('-')): selfcontained = True args.pop(0) if not args: sys.exit(0) modname = args.pop(0) plain_docs = args html_head = """\ %s %s """ html_foot = """\ """ def split_para(doc): p = [] for x in doc.split('\n\n'): x = x.strip() if x.find('\n>>>') > -1: h, t = x.split('\n>>>', 1) p.append(h) p.append('>>>' + t) else: p.append(x) return p def get_doc(members): try: doc = members['__doc__'] except KeyError: pass if doc: return doc try: doc = 'Python module %s' % members['__name__'] except KeyError: pass if doc: return doc else: return 'A Python module' def format_synopsis(synopsis, link=False, classname=None): lst = synopsis.split('\n') for i, s in zip(range(len(lst)), lst): m = re.match(r'(?P\w+)(?P.*)', s) args = re.sub(r'([a-zA-Z]\w+)', r'\1', m.group('args')) func = m.group('func') if link: if classname: func = '%s' % (classname, func, func) else: func = '%s' % (func, func) lst[i] = func + args return '
\n'.join(lst) def format_para(p): if not p: return '' doc = '' if p.startswith('>>>'): doc += '
\n%s\n
\n' % q(p) else: if not re.search('^- ', p, re.M): doc += '

%s

\n' % q(p) else: p = re.split('(?m)^- ', p) if p[0]: doc += '

%s

\n' % q(p[0].strip()) del p[0] doc += ('
    %s
\n' % '\n'.join(['
  • %s
  • ' % q(p.strip()) for p in p])) return doc def preprocess_routine(name, doc): parts = split_para(doc) if parts: summary = parts.pop(0) else: summary = 'FIXME' if parts and re.match(r'\w+\(.*\)', parts[0]): synopsis = parts.pop(0) else: synopsis = name + '()' return {'synopsis': synopsis, 'summary': summary, 'details': parts} def analyse(obj): members = obj.__dict__ if inspect.isclass(obj): main_doc = preprocess_routine(obj.__name__, get_doc(members)) bases = [x.__name__ for x in obj.__bases__] else: main_doc = split_para(get_doc(members)) bases = [] routines = {} classes = {} data = {} for name, m in members.items(): if name.startswith('__'): continue try: mro = list(inspect.getmro(m)) if mro[0] != m: continue except AttributeError: pass if inspect.isroutine(m): try: doc = m.__doc__ except KeyError: pass if not doc: doc = 'FIXME' routines[name] = preprocess_routine(name, doc) continue if inspect.isclass(m): classes[name] = analyse(m) continue t = type(m) if t == types.IntType or t == types.StringType: data[name] = repr(m) else: data[name] = m.__doc__ return {'name': obj.__name__, 'doc': main_doc, 'routines': routines, 'classes': classes, 'data': data, 'bases': bases} def format(tree, level, prefix=''): name = tree['name'] if prefix: fullname = '%s-%s' % (prefix, name) else: fullname = name ##### Main doc doc = [] if level > 1: doc = ['' % (level, fullname)] try: doc.append(format_synopsis(tree['doc']['synopsis'])) except TypeError: doc.append(name) doc.append('\n' % level) if tree.has_key('bases'): doc.append('

    Bases: %s.

    \n' % ', '.join(tree['bases'])) try: lst = [tree['doc']['summary']] + tree['doc']['details'] except TypeError: lst = tree['doc'] for p in lst: doc.append(format_para(p)) ##### Table of contents routines = tree['routines'].keys() classes = tree['classes'].keys() data = tree['data'].keys() if routines: routines.sort() if level == 1: doc.append('

    Functions:

    \n') else: doc.append('

    Methods:

    \n') doc.append('
      \n') for r in routines: synopsis = tree['routines'][r]['synopsis'] doc.append('
    • %s
    • \n' % format_synopsis(synopsis, True, fullname)) doc.append('
    \n') if classes: classes.sort() doc.append('

    Classes:

    \n') doc.append('
      \n') for r in classes: synopsis = tree['classes'][r]['doc']['synopsis'] doc.append('
    • %s
    • \n' % format_synopsis(synopsis, True, fullname)) doc.append('
    \n') if data: data.sort() doc.append('

    Data:

    \n') doc.append('
      \n') for r in data: doc.append('
    • %s = %s
    • \n' % (r, q(tree['data'][r]))) doc.append('
    \n') ##### Functions if routines: if level == 1: doc.append('
    \n') doc.append('
    \n') for r in routines: doc.append('
    ' % (fullname, r)) rt = tree['routines'][r] doc.append('%s
    \n
    ' % format_synopsis(rt['synopsis'])) for p in [rt['summary']] + rt['details']: doc.append(format_para(p)) doc.append('
    \n') doc.append('
    \n') ##### Classes if classes: for r in classes: doc.append('
    \n') doc.append(format(tree['classes'][r], level+1, fullname)) return ''.join(doc) exec 'import %s as __test__' % modname doctree = analyse(__test__) document = format(doctree, 1) print modname + '.html' fh = file(modname + '.html', 'w') if selfcontained: fh.write(html_head % (modname, 'module API')) fh.write(document) if selfcontained: fh.write(html_foot) fh.close() for f in plain_docs: try: fh = file(f, 'r') except: continue document = fh.read() fh.close() print f + '.xhtml' fh = file(f + '.xhtml', 'w') if selfcontained: fh.write(html_head % (modname, f)) fh.write('

    %s %s

    \n\n' % (modname, f)) fh.write('
    \n')
        fh.write(document)
        fh.write('
    \n') if selfcontained: fh.write(html_foot) fh.close()