2
#Copyright ReportLab Europe Ltd. 2000-2004
3
#see license.txt for license details
4
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/docpy.py
6
"""Generate documentation from live Python objects.
8
This is an evolving module that allows to generate documentation
9
for python modules in an automated fashion. The idea is to take
10
live Python objects and inspect them in order to use as much mean-
11
ingful information as possible to write in some formatted way into
12
different types of documents.
14
In principle a skeleton captures the gathered information and
15
makes it available via a certain API to formatters that use it
16
in whatever way they like to produce something of interest. The
17
API allows for adding behaviour in subclasses of these formatters,
18
such that, e.g. for certain classes it is possible to trigger
19
special actions like displaying a sample image of a class that
20
represents some graphical widget, say.
22
Type the following for usage info:
27
# Much inspired by Ka-Ping Yee's htmldoc.py.
28
# Needs the inspect module.
36
import sys, os, re, types, string, getopt, copy, time
37
from string import find, join, split, replace, expandtabs, rstrip
39
from reportlab.pdfgen import canvas
40
from reportlab.lib import colors
41
from reportlab.lib.units import inch, cm
42
from reportlab.lib.pagesizes import A4
43
from reportlab.lib import enums
44
from reportlab.lib.enums import TA_CENTER, TA_LEFT
45
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
46
from reportlab.platypus.flowables import Flowable, Spacer
47
from reportlab.platypus.paragraph import Paragraph
48
from reportlab.platypus.flowables \
49
import Flowable, Preformatted,Spacer, Image, KeepTogether, PageBreak
50
from reportlab.platypus.tableofcontents import TableOfContents
51
from reportlab.platypus.xpreformatted import XPreformatted
52
from reportlab.platypus.frames import Frame
53
from reportlab.platypus.doctemplate \
54
import PageTemplate, BaseDocTemplate
55
from reportlab.platypus.tables import TableStyle, Table
58
####################################################################
60
# Stuff needed for building PDF docs.
62
####################################################################
65
def mainPageFrame(canvas, doc):
66
"The page frame used for all PDF documents."
70
pageNumber = canvas.getPageNumber()
71
canvas.line(2*cm, A4[1]-2*cm, A4[0]-2*cm, A4[1]-2*cm)
72
canvas.line(2*cm, 2*cm, A4[0]-2*cm, 2*cm)
74
canvas.setFont('Times-Roman', 12)
75
canvas.drawString(4 * inch, cm, "%d" % pageNumber)
76
if hasattr(canvas, 'headerLine'): # hackish
77
headerline = string.join(canvas.headerLine, ' \215 ') # bullet
78
canvas.drawString(2*cm, A4[1]-1.75*cm, headerline)
80
canvas.setFont('Times-Roman', 8)
81
msg = "Generated with reportlab.lib.docpy. See http://www.reportlab.com!"
82
canvas.drawString(2*cm, 1.65*cm, msg)
87
class MyTemplate(BaseDocTemplate):
88
"The document template used for all PDF documents."
90
_invalidInitArgs = ('pageTemplates',)
92
def __init__(self, filename, **kw):
93
frame1 = Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')
94
self.allowSplitting = 0
95
apply(BaseDocTemplate.__init__, (self, filename), kw)
96
self.addPageTemplates(PageTemplate('normal', [frame1], mainPageFrame))
99
def afterFlowable(self, flowable):
100
"Takes care of header line, TOC and outline entries."
102
if flowable.__class__.__name__ == 'Paragraph':
104
name7 = f.style.name[:7]
105
name8 = f.style.name[:8]
107
# Build a list of heading parts.
108
# So far, this is the *last* item on the *previous* page...
109
if name7 == 'Heading' and not hasattr(self.canv, 'headerLine'):
110
self.canv.headerLine = []
112
if name8 == 'Heading0':
113
self.canv.headerLine = [f.text] # hackish
114
elif name8 == 'Heading1':
115
if len(self.canv.headerLine) == 2:
116
del self.canv.headerLine[-1]
117
elif len(self.canv.headerLine) == 3:
118
del self.canv.headerLine[-1]
119
del self.canv.headerLine[-1]
120
self.canv.headerLine.append(f.text)
121
elif name8 == 'Heading2':
122
if len(self.canv.headerLine) == 3:
123
del self.canv.headerLine[-1]
124
self.canv.headerLine.append(f.text)
126
if name7 == 'Heading':
127
# Register TOC entries.
128
headLevel = int(f.style.name[7:])
129
self.notify('TOCEntry', (headLevel, flowable.getPlainText(), self.page))
131
# Add PDF outline entries.
143
c.addOutlineEntry(title, key, level=headLevel,
149
####################################################################
151
# Utility functions (Ka-Ping Yee).
153
####################################################################
155
def htmlescape(text):
156
"Escape special HTML characters, namely &, <, >."
157
return replace(replace(replace(text, '&', '&'),
161
def htmlrepr(object):
162
return htmlescape(repr(object))
165
def defaultformat(object):
166
return '=' + htmlrepr(object)
170
result = inspect.getdoc(object)
173
result = inspect.getcomments(object)
176
return result and rstrip(result) + '\n' or ''
179
def reduceDocStringLength(docStr):
180
"Return first line of a multiline string."
182
return split(docStr, '\n')[0]
185
####################################################################
187
# More utility functions
189
####################################################################
191
def makeHtmlSection(text, bgcolor='#FFA0FF'):
192
"""Create HTML code for a section.
194
This is usually a header for all classes or functions.
196
text = htmlescape(expandtabs(text))
198
result.append("""<TABLE WIDTH="100\%" BORDER="0">""")
199
result.append("""<TR><TD BGCOLOR="%s" VALIGN="CENTER">""" % bgcolor)
200
result.append("""<H2>%s</H2>""" % text)
201
result.append("""</TD></TR></TABLE>""")
204
return join(result, '\n')
207
def makeHtmlSubSection(text, bgcolor='#AAA0FF'):
208
"""Create HTML code for a subsection.
210
This is usually a class or function name.
212
text = htmlescape(expandtabs(text))
214
result.append("""<TABLE WIDTH="100\%" BORDER="0">""")
215
result.append("""<TR><TD BGCOLOR="%s" VALIGN="CENTER">""" % bgcolor)
216
result.append("""<H3><TT><FONT SIZE="+2">%s</FONT></TT></H3>""" % text)
217
result.append("""</TD></TR></TABLE>""")
220
return join(result, '\n')
223
def makeHtmlInlineImage(text):
224
"""Create HTML code for an inline image.
227
return """<IMG SRC="%s" ALT="%s">""" % (text, text)
230
####################################################################
232
# Core "standard" docpy classes
234
####################################################################
236
class PackageSkeleton0:
237
"""A class collecting 'interesting' information about a package."""
241
class ModuleSkeleton0:
242
"""A class collecting 'interesting' information about a module."""
245
# This is an ad-hoc, somewhat questionable 'data structure',
246
# but for the time being it serves its purpose and is fairly
253
# Might need more like this, later.
254
def getModuleName(self):
255
"""Return the name of the module being treated."""
257
return self.module['name']
260
# These inspect methods all rely on the inspect module.
261
def inspect(self, object):
262
"""Collect information about a given object."""
264
self.moduleSpace = object
266
# Very non-OO, left for later...
267
if inspect.ismodule(object):
268
self._inspectModule(object)
269
elif inspect.isclass(object):
270
self._inspectClass(object)
271
elif inspect.ismethod(object):
272
self._inspectMethod(object)
273
elif inspect.isfunction(object):
274
self._inspectFunction(object)
275
elif inspect.isbuiltin(object):
276
self._inspectBuiltin(object)
278
msg = "Don't know how to document this kind of object."
282
def _inspectModule(self, object):
283
"""Collect information about a given module object."""
284
name = object.__name__
286
self.module['name'] = name
287
if hasattr(object, '__version__'):
288
self.module['version'] = object.__version__
290
cadr = lambda list: list[1]
291
modules = map(cadr, inspect.getmembers(object, inspect.ismodule))
293
classes, cdict = [], {}
294
for key, value in inspect.getmembers(object, inspect.isclass):
295
if (inspect.getmodule(value) or object) is object:
296
classes.append(value)
297
cdict[key] = cdict[value] = '#' + key
299
functions, fdict = [], {}
300
for key, value in inspect.getmembers(object, inspect.isroutine):
301
#if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
302
functions.append(value)
303
fdict[key] = '#-' + key
304
if inspect.isfunction(value): fdict[value] = fdict[key]
307
for base in c.__bases__:
308
key, modname = base.__name__, base.__module__
309
if modname != name and sys.modules.has_key(modname):
310
module = sys.modules[modname]
311
if hasattr(module, key) and getattr(module, key) is base:
312
if not cdict.has_key(key):
313
cdict[key] = cdict[base] = modname + '.txt#' + key
315
## doc = getdoc(object) or 'No doc string.'
317
self.module['doc'] = doc
320
self.module['importedModules'] = map(lambda m:m.__name__, modules)
324
self._inspectClass(item, fdict, cdict)
327
for item in functions:
328
self._inspectFunction(item, fdict, cdict)
331
def _inspectClass(self, object, functions={}, classes={}):
332
"""Collect information about a given class object."""
334
name = object.__name__
335
bases = object.__bases__
343
self.classes[name] = {}
345
self.classes[name]['bases'] = parents
347
methods, mdict = [], {}
348
for key, value in inspect.getmembers(object, inspect.ismethod):
349
methods.append(value)
350
mdict[key] = mdict[value] = '#' + name + '-' + key
353
if not self.classes[name].has_key('methods'):
354
self.classes[name]['methods'] = {}
356
self._inspectMethod(item, functions, classes, mdict, name)
358
## doc = getdoc(object) or 'No doc string.'
360
self.classes[name]['doc'] = doc
363
def _inspectMethod(self, object, functions={}, classes={}, methods={}, clname=''):
364
"""Collect information about a given method object."""
366
self._inspectFunction(object.im_func, functions, classes, methods, clname)
369
def _inspectFunction(self, object, functions={}, classes={}, methods={}, clname=''):
370
"""Collect information about a given function object."""
372
args, varargs, varkw, defaults = inspect.getargspec(object)
373
argspec = inspect.formatargspec(
374
args, varargs, varkw, defaults,
375
defaultformat=defaultformat)
379
## doc = getdoc(object) or 'No doc string.'
382
if object.__name__ == '<lambda>':
383
decl = [' lambda ', argspec[1:-1]]
385
# Do something with lambda functions as well...
388
decl = object.__name__
390
self.functions[object.__name__] = {'signature':argspec, 'doc':doc}
392
theMethods = self.classes[clname]['methods']
393
if not theMethods.has_key(object.__name__):
394
theMethods[object.__name__] = {}
396
theMethod = theMethods[object.__name__]
397
theMethod['signature'] = argspec
398
theMethod['doc'] = doc
401
def _inspectBuiltin(self, object):
402
"""Collect information about a given built-in."""
404
print object.__name__ + '( ... )'
407
def walk(self, formatter):
408
"""Call event methods in a visiting formatter."""
413
# The order is fixed, but could be made flexible
414
# with one more template method...
417
modName = s.module['name']
418
modDoc = s.module['doc']
419
imported = s.module.get('importedModules', [])
421
# f.indentLevel = f.indentLevel + 1
422
f.beginModule(modName, modDoc, imported)
425
f.indentLevel = f.indentLevel + 1
426
f.beginClasses(s.classes.keys())
427
items = s.classes.items()
430
cDoc = s.classes[k]['doc']
431
bases = s.classes[k].get('bases', [])
432
f.indentLevel = f.indentLevel + 1
433
f.beginClass(k, cDoc, bases)
435
# This if should move out of this method.
436
if not s.classes[k].has_key('methods'):
437
s.classes[k]['methods'] = {}
440
#f.indentLevel = f.indentLevel + 1
441
f.beginMethods(s.classes[k]['methods'].keys())
442
items = s.classes[k]['methods'].items()
447
f.indentLevel = f.indentLevel + 1
448
f.beginMethod(m, mDoc, sig)
449
f.indentLevel = f.indentLevel - 1
450
f.endMethod(m, mDoc, sig)
452
#f.indentLevel = f.indentLevel - 1
453
f.endMethods(s.classes[k]['methods'].keys())
455
f.indentLevel = f.indentLevel - 1
456
f.endClass(k, cDoc, bases)
458
# And what about attributes?!
460
f.indentLevel = f.indentLevel - 1
461
f.endClasses(s.classes.keys())
464
f.indentLevel = f.indentLevel + 1
465
f.beginFunctions(s.functions.keys())
466
items = s.functions.items()
471
f.indentLevel = f.indentLevel + 1
472
f.beginFunction(k, doc, sig)
473
f.indentLevel = f.indentLevel - 1
474
f.endFunction(k, doc, sig)
475
f.indentLevel = f.indentLevel - 1
476
f.endFunctions(s.functions.keys())
478
#f.indentLevel = f.indentLevel - 1
479
f.endModule(modName, modDoc, imported)
484
####################################################################
486
# Core "standard" docpy document builders
488
####################################################################
491
"""An abstract class to document the skeleton of a Python module.
493
Instances take a skeleton instance s and call their s.walk()
494
method. The skeleton, in turn, will walk over its tree structure
495
while generating events and calling equivalent methods from a
496
specific interface (begin/end methods).
501
def __init__(self, skeleton=None):
502
self.skeleton = skeleton
503
self.packageName = None
507
def write(self, skeleton=None):
509
self.skeleton = skeleton
510
self.skeleton.walk(self)
513
# Event-method API, called by associated skeleton instances.
514
# In fact, these should raise a NotImplementedError, but for now we
515
# just don't do anything here.
517
# The following four methods are *not* called by skeletons!
518
def begin(self, name='', typ=''): pass
521
# Methods for packaging should move into a future PackageSkeleton...
522
def beginPackage(self, name):
523
self.packageName = name
525
def endPackage(self, name):
528
# Only this subset is really called by associated skeleton instances.
530
def beginModule(self, name, doc, imported): pass
531
def endModule(self, name, doc, imported): pass
533
def beginClasses(self, names): pass
534
def endClasses(self, names): pass
536
def beginClass(self, name, doc, bases): pass
537
def endClass(self, name, doc, bases): pass
539
def beginMethods(self, names): pass
540
def endMethods(self, names): pass
542
def beginMethod(self, name, doc, sig): pass
543
def endMethod(self, name, doc, sig): pass
545
def beginFunctions(self, names): pass
546
def endFunctions(self, names): pass
548
def beginFunction(self, name, doc, sig): pass
549
def endFunction(self, name, doc, sig): pass
552
class AsciiDocBuilder0(DocBuilder0):
553
"""Document the skeleton of a Python module in ASCII format.
555
The output will be an ASCII file with nested lines representing
556
the hiearchical module structure.
558
Currently, no doc strings are listed.
566
# This if should move into DocBuilder0...
568
self.outPath = self.packageName + self.fileSuffix
570
self.outPath = self.skeleton.getModuleName() + self.fileSuffix
575
file = open(self.outPath, 'w')
576
for line in self.outLines:
577
file.write(line + '\n')
581
def beginPackage(self, name):
582
DocBuilder0.beginPackage(self, name)
583
lev, label = self.indentLevel, self.indentLabel
584
self.outLines.append('%sPackage: %s' % (lev*label, name))
585
self.outLines.append('')
588
def beginModule(self, name, doc, imported):
589
append = self.outLines.append
590
lev, label = self.indentLevel, self.indentLabel
591
self.outLines.append('%sModule: %s' % (lev*label, name))
592
## self.outLines.append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc)))
596
self.outLines.append('%sImported' % ((lev+1)*label))
599
self.outLines.append('%s%s' % ((lev+2)*label, m))
603
def beginClasses(self, names):
605
lev, label = self.indentLevel, self.indentLabel
606
self.outLines.append('%sClasses' % (lev*label))
607
self.outLines.append('')
610
def beginClass(self, name, doc, bases):
611
append = self.outLines.append
612
lev, label = self.indentLevel, self.indentLabel
615
bases = map(lambda b:b.__name__, bases) # hack
616
append('%s%s(%s)' % (lev*label, name, join(bases, ', ')))
618
append('%s%s' % (lev*label, name))
621
## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc)))
622
self.outLines.append('')
625
def endClass(self, name, doc, bases):
626
self.outLines.append('')
629
def beginMethod(self, name, doc, sig):
630
append = self.outLines.append
631
lev, label = self.indentLevel, self.indentLabel
632
append('%s%s%s' % (lev*label, name, sig))
633
## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc)))
637
def beginFunctions(self, names):
639
lev, label = self.indentLevel, self.indentLabel
640
self.outLines.append('%sFunctions' % (lev*label))
641
self.outLines.append('')
644
def endFunctions(self, names):
645
self.outLines.append('')
648
def beginFunction(self, name, doc, sig):
649
append = self.outLines.append
650
lev, label = self.indentLevel, self.indentLabel
651
self.outLines.append('%s%s%s' % (lev*label, name, sig))
652
## append('%s%s' % ((lev+1)*label, reduceDocStringLength(doc)))
656
class HtmlDocBuilder0(DocBuilder0):
657
"A class to write the skeleton of a Python source in HTML format."
662
def begin(self, name='', typ=''):
663
self.outLines.append("""<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">""")
664
self.outLines.append("""<html>""")
669
self.outPath = self.packageName + self.fileSuffix
671
self.outPath = self.skeleton.getModuleName() + self.fileSuffix
676
file = open(self.outPath, 'w')
677
self.outLines.append('</body></html>')
678
for line in self.outLines:
679
file.write(line + '\n')
683
def beginPackage(self, name):
684
DocBuilder0.beginPackage(self, name)
686
self.outLines.append("""<title>%s</title>""" % name)
687
self.outLines.append("""<body bgcolor="#ffffff">""")
688
self.outLines.append("""<H1>%s</H1>""" % name)
689
self.outLines.append('')
692
def beginModule(self, name, doc, imported):
693
if not self.packageName:
694
self.outLines.append("""<title>%s</title>""" % name)
695
self.outLines.append("""<body bgcolor="#ffffff">""")
697
self.outLines.append("""<H1>%s</H1>""" % name)
698
self.outLines.append('')
699
for line in split(doc, '\n'):
700
self.outLines.append("""<FONT SIZE="-1">%s</FONT>""" % htmlescape(line))
701
self.outLines.append('<BR>')
702
self.outLines.append('')
705
self.outLines.append(makeHtmlSection('Imported Modules'))
706
self.outLines.append("""<ul>""")
708
self.outLines.append("""<li>%s</li>""" % m)
709
self.outLines.append("""</ul>""")
712
def beginClasses(self, names):
713
self.outLines.append(makeHtmlSection('Classes'))
716
def beginClass(self, name, doc, bases):
717
DocBuilder0.beginClass(self, name, doc, bases)
719
## # Keep an eye on the base classes.
720
## self.currentBaseClasses = bases
723
bases = map(lambda b:b.__name__, bases) # hack
724
self.outLines.append(makeHtmlSubSection('%s(%s)' % (name, join(bases, ', '))))
726
self.outLines.append(makeHtmlSubSection('%s' % name))
727
for line in split(doc, '\n'):
728
self.outLines.append("""<FONT SIZE="-1">%s</FONT>""" % htmlescape(line))
729
self.outLines.append('<BR>')
731
self.outLines.append('')
734
def beginMethods(self, names):
737
## self.outLines.append('<H3>Method Interface</H3>')
738
## self.outLines.append('')
741
def beginMethod(self, name, doc, sig):
742
self.beginFunction(name, doc, sig)
745
def beginFunctions(self, names):
746
self.outLines.append(makeHtmlSection('Functions'))
749
def beginFunction(self, name, doc, sig):
750
append = self.outLines.append
751
append("""<DL><DL><DT><TT><STRONG>%s</STRONG>%s</TT></DT>""" % (name, sig))
753
for line in split(doc, '\n'):
754
append("""<DD><FONT SIZE="-1">%s</FONT></DD>""" % htmlescape(line))
760
class PdfDocBuilder0(DocBuilder0):
761
"Document the skeleton of a Python module in PDF format."
765
def makeHeadingStyle(self, level, typ=None, doc=''):
766
"Make a heading style for different types of module content."
768
if typ in ('package', 'module', 'class'):
769
style = ParagraphStyle(name='Heading'+str(level),
770
fontName = 'Courier-Bold',
775
elif typ in ('method', 'function'):
777
style = ParagraphStyle(name='Heading'+str(level),
778
fontName = 'Courier-Bold',
786
style = ParagraphStyle(name='Heading'+str(level),
787
fontName = 'Courier-Bold',
796
style = ParagraphStyle(name='Heading'+str(level),
797
fontName = 'Times-Bold',
806
def begin(self, name='', typ=''):
807
styleSheet = getSampleStyleSheet()
808
self.code = styleSheet['Code']
809
self.bt = styleSheet['BodyText']
813
t = time.gmtime(time.time())
814
timeString = time.strftime("%Y-%m-%d %H:%M", t)
815
self.story.append(Paragraph('<font size=18>Documentation for %s "%s"</font>' % (typ, name), self.bt))
816
self.story.append(Paragraph('<font size=18>Generated by: docpy.py version %s</font>' % __version__, self.bt))
817
self.story.append(Paragraph('<font size=18>Date generated: %s</font>' % timeString, self.bt))
818
self.story.append(Paragraph('<font size=18>Format: PDF</font>', self.bt))
819
self.story.append(PageBreak())
822
toc = TableOfContents()
823
self.story.append(toc)
824
self.story.append(PageBreak())
828
if self.outPath is not None:
830
elif self.packageName:
831
self.outPath = self.packageName + self.fileSuffix
833
self.outPath = self.skeleton.getModuleName() + self.fileSuffix
836
print 'output path is %s' % self.outPath
838
doc = MyTemplate(self.outPath)
839
doc.multiBuild(self.story)
842
def beginPackage(self, name):
843
DocBuilder0.beginPackage(self, name)
845
story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'package')))
848
def beginModule(self, name, doc, imported):
851
story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'module')))
853
story.append(XPreformatted(htmlescape(doc), bt))
854
story.append(XPreformatted('', bt))
857
story.append(Paragraph('Imported modules', self.makeHeadingStyle(self.indentLevel + 1)))
859
p = Paragraph('<bullet>\201</bullet> %s' % m, bt)
860
p.style.bulletIndent = 10
861
p.style.leftIndent = 18
865
def endModule(self, name, doc, imported):
866
DocBuilder0.endModule(self, name, doc, imported)
867
self.story.append(PageBreak())
870
def beginClasses(self, names):
871
self.story.append(Paragraph('Classes', self.makeHeadingStyle(self.indentLevel)))
874
def beginClass(self, name, doc, bases):
878
bases = map(lambda b:b.__name__, bases) # hack
879
story.append(Paragraph('%s(%s)' % (name, join(bases, ', ')), self.makeHeadingStyle(self.indentLevel, 'class')))
881
story.append(Paragraph(name, self.makeHeadingStyle(self.indentLevel, 'class')))
884
story.append(XPreformatted(htmlescape(doc), bt))
885
story.append(XPreformatted('', bt))
888
def beginMethod(self, name, doc, sig):
891
story.append(Paragraph(name+sig, self.makeHeadingStyle(self.indentLevel, 'method', doc)))
893
story.append(XPreformatted(htmlescape(doc), bt))
894
story.append(XPreformatted('', bt))
897
def beginFunctions(self, names):
899
self.story.append(Paragraph('Functions', self.makeHeadingStyle(self.indentLevel)))
902
def beginFunction(self, name, doc, sig):
905
story.append(Paragraph(name+sig, self.makeHeadingStyle(self.indentLevel, 'function')))
907
story.append(XPreformatted(htmlescape(doc), bt))
908
story.append(XPreformatted('', bt))
911
class UmlPdfDocBuilder0(PdfDocBuilder0):
912
"Document the skeleton of a Python module with UML class diagrams."
916
def begin(self, name='', typ=''):
917
styleSheet = getSampleStyleSheet()
918
self.h1 = styleSheet['Heading1']
919
self.h2 = styleSheet['Heading2']
920
self.h3 = styleSheet['Heading3']
921
self.code = styleSheet['Code']
922
self.bt = styleSheet['BodyText']
924
self.classCompartment = ''
925
self.methodCompartment = []
928
def beginModule(self, name, doc, imported):
930
h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt
931
styleSheet = getSampleStyleSheet()
932
bt1 = styleSheet['BodyText']
934
story.append(Paragraph(name, h1))
935
story.append(XPreformatted(doc, bt1))
938
story.append(Paragraph('Imported modules', self.makeHeadingStyle(self.indentLevel + 1)))
940
p = Paragraph('<bullet>\201</bullet> %s' % m, bt1)
941
p.style.bulletIndent = 10
942
p.style.leftIndent = 18
946
def endModule(self, name, doc, imported):
947
self.story.append(PageBreak())
948
PdfDocBuilder0.endModule(self, name, doc, imported)
951
def beginClasses(self, names):
952
h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt
954
self.story.append(Paragraph('Classes', h2))
957
def beginClass(self, name, doc, bases):
958
self.classCompartment = ''
959
self.methodCompartment = []
962
bases = map(lambda b:b.__name__, bases) # hack
963
self.classCompartment = '%s(%s)' % (name, join(bases, ', '))
965
self.classCompartment = name
968
def endClass(self, name, doc, bases):
969
h1, h2, h3, bt, code = self.h1, self.h2, self.h3, self.bt, self.code
970
styleSheet = getSampleStyleSheet()
971
bt1 = styleSheet['BodyText']
974
# Use only the first line of the class' doc string --
975
# no matter how long! (Do the same later for methods)
976
classDoc = reduceDocStringLength(doc)
978
tsa = tableStyleAttributes = []
980
# Make table with class and method rows
981
# and add it to the story.
982
p = Paragraph('<b>%s</b>' % self.classCompartment, bt)
983
p.style.alignment = TA_CENTER
985
# No doc strings, now...
986
# rows = rows + [(Paragraph('<i>%s</i>' % classDoc, bt1),)]
988
tsa.append(('BOX', (0,0), (-1,lenRows-1), 0.25, colors.black))
989
for name, doc, sig in self.methodCompartment:
990
nameAndSig = Paragraph('<b>%s</b>%s' % (name, sig), bt1)
991
rows.append((nameAndSig,))
992
# No doc strings, now...
993
# docStr = Paragraph('<i>%s</i>' % reduceDocStringLength(doc), bt1)
994
# rows.append((docStr,))
995
tsa.append(('BOX', (0,lenRows), (-1,-1), 0.25, colors.black))
996
t = Table(rows, (12*cm,))
997
tableStyle = TableStyle(tableStyleAttributes)
998
t.setStyle(tableStyle)
1000
self.story.append(Spacer(1*cm, 1*cm))
1003
def beginMethod(self, name, doc, sig):
1004
self.methodCompartment.append((name, doc, sig))
1007
def beginFunctions(self, names):
1008
h1, h2, h3, bt = self.h1, self.h2, self.h3, self.bt
1010
self.story.append(Paragraph('Functions', h2))
1011
self.classCompartment = chr(171) + ' Module-Level Functions ' + chr(187)
1012
self.methodCompartment = []
1015
def beginFunction(self, name, doc, sig):
1016
self.methodCompartment.append((name, doc, sig))
1019
def endFunctions(self, names):
1020
h1, h2, h3, bt, code = self.h1, self.h2, self.h3, self.bt, self.code
1021
styleSheet = getSampleStyleSheet()
1022
bt1 = styleSheet['BodyText']
1027
tsa = tableStyleAttributes = []
1029
# Make table with class and method rows
1030
# and add it to the story.
1031
p = Paragraph('<b>%s</b>' % self.classCompartment, bt)
1032
p.style.alignment = TA_CENTER
1035
tsa.append(('BOX', (0,0), (-1,lenRows-1), 0.25, colors.black))
1036
for name, doc, sig in self.methodCompartment:
1037
nameAndSig = Paragraph('<b>%s</b>%s' % (name, sig), bt1)
1038
rows.append((nameAndSig,))
1039
# No doc strings, now...
1040
# docStr = Paragraph('<i>%s</i>' % reduceDocStringLength(doc), bt1)
1041
# rows.append((docStr,))
1042
tsa.append(('BOX', (0,lenRows), (-1,-1), 0.25, colors.black))
1043
t = Table(rows, (12*cm,))
1044
tableStyle = TableStyle(tableStyleAttributes)
1045
t.setStyle(tableStyle)
1046
self.story.append(t)
1047
self.story.append(Spacer(1*cm, 1*cm))
1050
####################################################################
1054
####################################################################
1057
"""docpy.py - Automated documentation for Python source code.
1059
Usage: python docpy.py [options]
1062
-h Print this help message.
1064
-f name Use the document builder indicated by 'name',
1065
e.g. Ascii, Html, Pdf (default), UmlPdf.
1067
-m module Generate document for module named 'module'
1068
(default is 'docpy').
1069
'module' may follow any of these forms:
1073
and can be any of these:
1074
- standard Python modules
1075
- modules in the Python search path
1076
- modules in the current directory
1078
-p package Generate document for package named 'package'.
1079
'package' may follow any of these forms:
1081
- reportlab.platypus
1082
- c:\\test\\reportlab
1083
and can be any of these:
1084
- standard Python packages (?)
1085
- packages in the Python search path
1086
- packages in the current directory
1088
-s Silent mode (default is unset).
1093
python docpy.py -m docpy.py -f Ascii
1094
python docpy.py -m string -f Html
1095
python docpy.py -m signsandsymbols.py -f Pdf
1096
python docpy.py -p reportlab.platypus -f UmlPdf
1097
python docpy.py -p reportlab.lib -s -f UmlPdf
1101
def documentModule0(pathOrName, builder, opts={}):
1102
"""Generate documentation for one Python file in some format.
1104
This handles Python standard modules like string, custom modules
1105
on the Python search path like e.g. docpy as well as modules
1106
specified with their full path like C:/tmp/junk.py.
1108
The doc file will always be saved in the current directory with
1109
a basename equal to that of the module, e.g. docpy.
1114
# Append directory to Python search path if we get one.
1115
dirName = os.path.dirname(pathOrName)
1117
sys.path.append(dirName)
1119
# Remove .py extension from module name.
1120
if pathOrName[-3:] == '.py':
1121
modname = pathOrName[:-3]
1123
modname = pathOrName
1125
# Remove directory paths from module name.
1127
modname = os.path.basename(modname)
1131
module = __import__(modname)
1133
print 'Failed to import %s.' % modname
1137
# Do the real documentation work.
1138
s = ModuleSkeleton0()
1142
# Remove appended directory from Python search path if we got one.
1149
def _packageWalkCallback((builder, opts), dirPath, files):
1150
"A callback function used when waking over a package tree."
1152
# Skip __init__ files.
1153
files = filter(lambda f:f != '__init__.py', files)
1155
files = filter(lambda f:f[-3:] == '.py', files)
1157
path = os.path.join(dirPath, f)
1158
if not opts.get('isSilent', 0):
1160
builder.indentLevel = builder.indentLevel + 1
1161
documentModule0(path, builder)
1162
builder.indentLevel = builder.indentLevel - 1
1165
def documentPackage0(pathOrName, builder, opts={}):
1166
"""Generate documentation for one Python package in some format.
1168
'pathOrName' can be either a filesystem path leading to a Python
1169
package or package name whose path will be resolved by importing
1170
the top-level module.
1172
The doc file will always be saved in the current directory with
1173
a basename equal to that of the package, e.g. reportlab.lib.
1176
# Did we get a package path with OS-dependant seperators...?
1177
if os.sep in pathOrName:
1179
name = os.path.splitext(os.path.basename(path))[0]
1180
# ... or rather a package name?
1183
package = __import__(name)
1184
# Some special care needed for dotted names.
1186
subname = 'package' + name[find(name, '.'):]
1187
package = eval(subname)
1188
path = os.path.dirname(package.__file__)
1191
builder.beginPackage(name)
1192
os.path.walk(path, _packageWalkCallback, (builder, opts))
1193
builder.endPackage(name)
1198
"Handle command-line options and trigger corresponding action."
1200
opts, args = getopt.getopt(sys.argv[1:], 'hsf:m:p:')
1202
# Make an options dictionary that is easier to use.
1206
hasOpt = optsDict.has_key
1208
# On -h print usage and exit immediately.
1210
print printUsage.__doc__
1213
# On -s set silent mode.
1214
isSilent = hasOpt('-s')
1216
# On -f set the appropriate DocBuilder to use or a default one.
1217
builderClassName = optsDict.get('-f', 'Pdf') + 'DocBuilder0'
1218
builder = eval(builderClassName + '()')
1220
# Set default module or package to document.
1221
if not hasOpt('-p') and not hasOpt('-m'):
1222
optsDict['-m'] = 'docpy'
1224
# Save a few options for further use.
1225
options = {'isSilent':isSilent}
1227
# Now call the real documentation functions.
1229
nameOrPath = optsDict['-m']
1231
print "Generating documentation for module %s..." % nameOrPath
1232
builder.begin(name=nameOrPath, typ='module')
1233
documentModule0(nameOrPath, builder, options)
1235
nameOrPath = optsDict['-p']
1237
print "Generating documentation for package %s..." % nameOrPath
1238
builder.begin(name=nameOrPath, typ='package')
1239
documentPackage0(nameOrPath, builder, options)
1243
print "Saved %s." % builder.outPath
1246
if __name__ == '__main__':