blob: 9dc359f8355c8f80fdb01876e1a8299d540e95bb [file] [log] [blame]
Ka-Ping Yeedd175342001-02-27 14:43:46 +00001#!/usr/bin/env python
2"""Generate Python documentation in HTML or as text for interactive use.
3
4At the shell command line outside of Python, run "pydoc <name>" to show
5documentation on something. <name> may be the name of a Python function,
6module, package, or a dotted reference to a class or function within a
7module or module in a package. Alternatively, the argument can be the
8path to a Python source file.
9
10Or, at the shell prompt, run "pydoc -k <keyword>" to search for a keyword
11in the one-line descriptions of modules.
12
13Or, at the shell prompt, run "pydoc -p <port>" to start an HTTP server
14on a given port on the local machine to generate documentation web pages.
15
16Or, at the shell prompt, run "pydoc -w <name>" to write out the HTML
17documentation for a module to a file named "<name>.html".
18
19In the Python interpreter, do "from pydoc import help" to provide online
20help. Calling help(thing) on a Python object documents the object."""
21
22__author__ = "Ka-Ping Yee <ping@lfw.org>"
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +000023__date__ = "26 February 2001"
24__version__ = "$Revision $"
25__credits__ = """Tommy Burnette, the original creator of manpy.
26Paul Prescod, for all his work on onlinehelp.
27Richard Chamberlain, for the first implementation of textdoc.
28
29A moose bit my sister once."""
Ka-Ping Yeedd175342001-02-27 14:43:46 +000030
31import sys, imp, os, stat, re, types, inspect
32from repr import Repr
33from string import expandtabs, find, join, lower, split, strip, rstrip
34
35# --------------------------------------------------------- common routines
36
37def synopsis(filename, cache={}):
38 """Get the one-line summary out of a module file."""
39 mtime = os.stat(filename)[stat.ST_MTIME]
40 lastupdate, result = cache.get(filename, (0, None))
41 if lastupdate < mtime:
42 file = open(filename)
43 line = file.readline()
44 while line[:1] == '#' or strip(line) == '':
45 line = file.readline()
46 if not line: break
47 if line[-2:] == '\\\n':
48 line = line[:-2] + file.readline()
49 line = strip(line)
50 if line[:3] == '"""':
51 line = line[3:]
52 while strip(line) == '':
53 line = file.readline()
54 if not line: break
55 result = split(line, '"""')[0]
56 else: result = None
57 file.close()
58 cache[filename] = (mtime, result)
59 return result
60
61def index(dir):
62 """Return a list of (module-name, synopsis) pairs for a directory tree."""
63 results = []
64 for entry in os.listdir(dir):
65 path = os.path.join(dir, entry)
66 if ispackage(path):
67 results.extend(map(
68 lambda (m, s), pkg=entry: (pkg + '.' + m, s), index(path)))
69 elif os.path.isfile(path) and entry[-3:] == '.py':
70 results.append((entry[:-3], synopsis(path)))
71 return results
72
73def pathdirs():
74 """Convert sys.path into a list of absolute, existing, unique paths."""
75 dirs = []
76 for dir in sys.path:
77 dir = os.path.abspath(dir or '.')
78 if dir not in dirs and os.path.isdir(dir):
79 dirs.append(dir)
80 return dirs
81
82def getdoc(object):
83 """Get the doc string or comments for an object."""
84 result = inspect.getdoc(object)
85 if not result:
86 try: result = inspect.getcomments(object)
87 except: pass
88 return result and rstrip(result) or ''
89
90def classname(object, modname):
91 """Get a class name and qualify it with a module name if necessary."""
92 name = object.__name__
93 if object.__module__ != modname:
94 name = object.__module__ + '.' + name
95 return name
96
97def isconstant(object):
98 """Check if an object is of a type that probably means it's a constant."""
99 return type(object) in [
100 types.FloatType, types.IntType, types.ListType, types.LongType,
101 types.StringType, types.TupleType, types.TypeType,
102 hasattr(types, 'UnicodeType') and types.UnicodeType or 0]
103
104def replace(text, *pairs):
105 """Do a series of global replacements on a string."""
106 for old, new in pairs:
107 text = join(split(text, old), new)
108 return text
109
110def cram(text, maxlen):
111 """Omit part of a string if needed to make it fit in a maximum length."""
112 if len(text) > maxlen:
113 pre = max(0, (maxlen-3)/2)
114 post = max(0, maxlen-3-pre)
115 return text[:pre] + '...' + text[len(text)-post:]
116 return text
117
118def cleanid(text):
119 """Remove the hexadecimal id from a Python object representation."""
120 return re.sub(' at 0x[0-9a-f]{5,}>$', '>', text)
121
122def modulename(path):
123 """Return the Python module name for a given path, or None."""
124 filename = os.path.basename(path)
125 if lower(filename[-3:]) == '.py':
126 return filename[:-3]
127 elif lower(filename[-4:]) == '.pyc':
128 return filename[:-4]
129 elif lower(filename[-11:]) == 'module.so':
130 return filename[:-11]
131 elif lower(filename[-13:]) == 'module.so.1':
132 return filename[:-13]
133
134class DocImportError(Exception):
135 """Class for errors while trying to import something to document it."""
136 def __init__(self, filename, etype, evalue):
137 self.filename = filename
138 self.etype = etype
139 self.evalue = evalue
140 if type(etype) is types.ClassType:
141 etype = etype.__name__
142 self.args = '%s: %s' % (etype, evalue)
143
144def importfile(path):
145 """Import a Python source file or compiled file given its path."""
146 magic = imp.get_magic()
147 file = open(path, 'r')
148 if file.read(len(magic)) == magic:
149 kind = imp.PY_COMPILED
150 else:
151 kind = imp.PY_SOURCE
152 file.close()
153 filename = os.path.basename(path)
154 name, ext = os.path.splitext(filename)
155 file = open(path, 'r')
156 try:
157 module = imp.load_module(name, file, path, (ext, 'r', kind))
158 except:
159 raise DocImportError(path, sys.exc_type, sys.exc_value)
160 file.close()
161 return module
162
163def ispackage(path):
164 """Guess whether a path refers to a package directory."""
165 if os.path.isdir(path):
166 init = os.path.join(path, '__init__.py')
167 initc = os.path.join(path, '__init__.pyc')
168 if os.path.isfile(init) or os.path.isfile(initc):
169 return 1
170
171# ---------------------------------------------------- formatter base class
172
173class Doc:
174 def document(self, object, *args):
175 """Generate documentation for an object."""
176 args = (object,) + args
177 if inspect.ismodule(object): return apply(self.docmodule, args)
178 if inspect.isclass(object): return apply(self.docclass, args)
179 if inspect.ismethod(object): return apply(self.docmethod, args)
180 if inspect.isbuiltin(object): return apply(self.docbuiltin, args)
181 if inspect.isfunction(object): return apply(self.docfunction, args)
182 raise TypeError, "don't know how to document objects of type " + \
183 type(object).__name__
184
185# -------------------------------------------- HTML documentation generator
186
187class HTMLRepr(Repr):
188 """Class for safely making an HTML representation of a Python object."""
189 def __init__(self):
190 Repr.__init__(self)
191 self.maxlist = self.maxtuple = self.maxdict = 10
192 self.maxstring = self.maxother = 50
193
194 def escape(self, text):
195 return replace(text, ('&', '&amp;'), ('<', '&lt;'), ('>', '&gt;'))
196
197 def repr(self, object):
198 result = Repr.repr(self, object)
199 return result
200
201 def repr1(self, x, level):
202 methodname = 'repr_' + join(split(type(x).__name__), '_')
203 if hasattr(self, methodname):
204 return getattr(self, methodname)(x, level)
205 else:
206 return self.escape(cram(cleanid(repr(x)), self.maxother))
207
208 def repr_string(self, x, level):
209 text = self.escape(cram(x, self.maxstring))
210 return re.sub(r'((\\[\\abfnrtv]|\\x..|\\u....)+)',
211 r'<font color="#c040c0">\1</font>', repr(text))
212
213 def repr_instance(self, x, level):
214 try:
215 return cram(cleanid(repr(x)), self.maxstring)
216 except:
217 return self.escape('<%s instance>' % x.__class__.__name__)
218
219 repr_unicode = repr_string
220
221class HTMLDoc(Doc):
222 """Formatter class for HTML documentation."""
223
224 # ------------------------------------------- HTML formatting utilities
225
226 _repr_instance = HTMLRepr()
227 repr = _repr_instance.repr
228 escape = _repr_instance.escape
229
230 def preformat(self, text):
231 """Format literal preformatted text."""
232 text = self.escape(expandtabs(text))
233 return replace(text, ('\n\n', '\n \n'), ('\n\n', '\n \n'),
234 (' ', '&nbsp;'), ('\n', '<br>\n'))
235
236 def multicolumn(self, list, format, cols=4):
237 """Format a list of items into a multi-column list."""
238 result = ''
239 rows = (len(list)+cols-1)/cols
240
241 for col in range(cols):
242 result = result + '<td width="%d%%" valign=top>' % (100/cols)
243 for i in range(rows*col, rows*col+rows):
244 if i < len(list):
245 result = result + format(list[i]) + '<br>'
246 result = result + '</td>'
247 return '<table width="100%%"><tr>%s</tr></table>' % result
248
249 def heading(self, title, fgcol, bgcol, extras=''):
250 """Format a page heading."""
251 return """
252<p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
253<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small
254><font color="%s" face="helvetica, arial">&nbsp;%s</font></td
255><td align=right valign=bottom
256><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr></table>
257 """ % (bgcol, fgcol, title, fgcol, extras)
258
259 def section(self, title, fgcol, bgcol, contents, width=20,
260 prelude='', marginalia=None, gap='&nbsp;&nbsp;&nbsp;'):
261 """Format a section with a heading."""
262 if marginalia is None:
263 marginalia = '&nbsp;' * width
264 result = """
265<p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
266<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small
267><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr>
268 """ % (bgcol, fgcol, title)
269 if prelude:
270 result = result + """
271<tr><td bgcolor="%s">%s</td>
272<td bgcolor="%s" colspan=2>%s</td></tr>
273 """ % (bgcol, marginalia, bgcol, prelude)
274 result = result + """
275<tr><td bgcolor="%s">%s</td><td>%s</td>
276 """ % (bgcol, marginalia, gap)
277
278 result = result + '<td width="100%%">%s</td></tr></table>' % contents
279 return result
280
281 def bigsection(self, title, *args):
282 """Format a section with a big heading."""
283 title = '<big><strong>%s</strong></big>' % title
284 return apply(self.section, (title,) + args)
285
286 def footer(self):
287 return """
288<table width="100%"><tr><td align=right>
289<font face="helvetica, arial"><small><small>generated with
290<strong>htmldoc</strong> by Ka-Ping Yee</a></small></small></font>
291</td></tr></table>
292 """
293
294 def namelink(self, name, *dicts):
295 """Make a link for an identifier, given name-to-URL mappings."""
296 for dict in dicts:
297 if dict.has_key(name):
298 return '<a href="%s">%s</a>' % (dict[name], name)
299 return name
300
301 def classlink(self, object, modname, *dicts):
302 """Make a link for a class."""
303 name = object.__name__
304 if object.__module__ != modname:
305 name = object.__module__ + '.' + name
306 for dict in dicts:
307 if dict.has_key(object):
308 return '<a href="%s">%s</a>' % (dict[object], name)
309 return name
310
311 def modulelink(self, object):
312 """Make a link for a module."""
313 return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
314
315 def modpkglink(self, (name, path, ispackage, shadowed)):
316 """Make a link for a module or package to display in an index."""
317 if shadowed:
318 return '<font color="#909090">%s</font>' % name
319 if path:
320 url = '%s.%s.html' % (path, name)
321 else:
322 url = '%s.html' % name
323 if ispackage:
324 text = '<strong>%s</strong>&nbsp;(package)' % name
325 else:
326 text = name
327 return '<a href="%s">%s</a>' % (url, text)
328
329 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
330 """Mark up some plain text, given a context of symbols to look for.
331 Each context dictionary maps object names to anchor names."""
332 escape = escape or self.escape
333 results = []
334 here = 0
335 pattern = re.compile(r'\b(((http|ftp)://\S+[\w/])|'
336 r'(RFC[- ]?(\d+))|'
337 r'(self\.)?(\w+))\b')
338 while 1:
339 match = pattern.search(text, here)
340 if not match: break
341 start, end = match.span()
342 results.append(escape(text[here:start]))
343
344 all, url, scheme, rfc, rfcnum, selfdot, name = match.groups()
345 if url:
346 results.append('<a href="%s">%s</a>' % (url, escape(url)))
347 elif rfc:
348 url = 'http://www.rfc-editor.org/rfc/rfc%s.txt' % rfcnum
349 results.append('<a href="%s">%s</a>' % (url, escape(rfc)))
350 else:
351 if text[end:end+1] == '(':
352 results.append(self.namelink(name, methods, funcs, classes))
353 elif selfdot:
354 results.append('self.<strong>%s</strong>' % name)
355 else:
356 results.append(self.namelink(name, classes))
357 here = end
358 results.append(escape(text[here:]))
359 return join(results, '')
360
361 # ---------------------------------------------- type-specific routines
362
363 def doctree(self, tree, modname, classes={}, parent=None):
364 """Produce HTML for a class tree as given by inspect.getclasstree()."""
365 result = ''
366 for entry in tree:
367 if type(entry) is type(()):
368 c, bases = entry
369 result = result + '<dt><font face="helvetica, arial"><small>'
370 result = result + self.classlink(c, modname, classes)
371 if bases and bases != (parent,):
372 parents = []
373 for base in bases:
374 parents.append(self.classlink(base, modname, classes))
375 result = result + '(' + join(parents, ', ') + ')'
376 result = result + '\n</small></font></dt>'
377 elif type(entry) is type([]):
378 result = result + \
379 '<dd>\n%s</dd>\n' % self.doctree(entry, modname, classes, c)
380 return '<dl>\n%s</dl>\n' % result
381
382 def docmodule(self, object):
383 """Produce HTML documentation for a module object."""
384 name = object.__name__
385 result = ''
386 head = '<br><big><big><strong>&nbsp;%s</strong></big></big>' % name
387 try:
388 file = inspect.getsourcefile(object)
389 filelink = '<a href="file:%s">%s</a>' % (file, file)
390 except TypeError:
391 filelink = '(built-in)'
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000392 info = []
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000393 if hasattr(object, '__version__'):
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000394 version = str(object.__version__)
395 if version[:11] == '$Revision$':
396 version = version[11:-1]
397 info.append('version: %s' % self.escape(version))
398 if hasattr(object, '__date__'):
399 info.append(self.escape(str(object.__date__)))
400 if info:
401 head = head + ' (%s)' % join(info, ', ')
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000402 result = result + self.heading(
403 head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
404
405 second = lambda list: list[1]
406 modules = map(second, inspect.getmembers(object, inspect.ismodule))
407
408 classes, cdict = [], {}
409 for key, value in inspect.getmembers(object, inspect.isclass):
410 if (inspect.getmodule(value) or object) is object:
411 classes.append(value)
412 cdict[key] = cdict[value] = '#' + key
413 funcs, fdict = [], {}
414 for key, value in inspect.getmembers(object, inspect.isroutine):
415 if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
416 funcs.append(value)
417 fdict[key] = '#-' + key
418 if inspect.isfunction(value): fdict[value] = fdict[key]
419 for c in classes:
420 for base in c.__bases__:
421 key, modname = base.__name__, base.__module__
422 if modname != name and sys.modules.has_key(modname):
423 module = sys.modules[modname]
424 if hasattr(module, key) and getattr(module, key) is base:
425 if not cdict.has_key(key):
426 cdict[key] = cdict[base] = modname + '.html#' + key
427 constants = []
428 for key, value in inspect.getmembers(object, isconstant):
429 if key[:1] != '_':
430 constants.append((key, value))
431
432 doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
433 doc = doc and '<tt>%s</tt>' % doc
434 result = result + '<p><small>%s</small></p>\n' % doc
435
436 if hasattr(object, '__path__'):
437 modpkgs = []
438 modnames = []
439 for file in os.listdir(object.__path__[0]):
440 if file[:1] != '_':
441 path = os.path.join(object.__path__[0], file)
442 modname = modulename(file)
443 if modname and modname not in modnames:
444 modpkgs.append((modname, name, 0, 0))
445 modnames.append(modname)
446 elif ispackage(path):
447 modpkgs.append((file, name, 1, 0))
448 modpkgs.sort()
449 contents = self.multicolumn(modpkgs, self.modpkglink)
450 result = result + self.bigsection(
451 'Package Contents', '#ffffff', '#aa55cc', contents)
452
453 elif modules:
454 contents = self.multicolumn(modules, self.modulelink)
455 result = result + self.bigsection(
456 'Modules', '#fffff', '#aa55cc', contents)
457
458 if classes:
459 contents = self.doctree(
460 inspect.getclasstree(classes, 1), name, cdict)
461 for item in classes:
462 contents = contents + self.document(item, fdict, cdict)
463 result = result + self.bigsection(
464 'Classes', '#ffffff', '#ee77aa', contents)
465 if funcs:
466 contents = ''
467 for item in funcs:
468 contents = contents + self.document(item, fdict, cdict)
469 result = result + self.bigsection(
470 'Functions', '#ffffff', '#eeaa77', contents)
471
472 if constants:
473 contents = ''
474 for key, value in constants:
475 contents = contents + ('<br><strong>%s</strong> = %s' %
476 (key, self.repr(value)))
477 result = result + self.bigsection(
478 'Constants', '#ffffff', '#55aa55', contents)
479
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000480 if hasattr(object, '__author__'):
481 contents = self.markup(str(object.__author__), self.preformat)
482 result = result + self.bigsection(
483 'Author', '#ffffff', '#7799ee', contents)
484
485 if hasattr(object, '__credits__'):
486 contents = self.markup(str(object.__credits__), self.preformat)
487 result = result + self.bigsection(
488 'Credits', '#ffffff', '#7799ee', contents)
489
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000490 return result
491
492 def docclass(self, object, funcs={}, classes={}):
493 """Produce HTML documentation for a class object."""
494 name = object.__name__
495 bases = object.__bases__
496 contents = ''
497
498 methods, mdict = [], {}
499 for key, value in inspect.getmembers(object, inspect.ismethod):
500 methods.append(value)
501 mdict[key] = mdict[value] = '#' + name + '-' + key
502 for item in methods:
503 contents = contents + self.document(
504 item, funcs, classes, mdict, name)
505
506 title = '<a name="%s">class <strong>%s</strong></a>' % (name, name)
507 if bases:
508 parents = []
509 for base in bases:
510 parents.append(self.classlink(base, object.__module__, classes))
511 title = title + '(%s)' % join(parents, ', ')
512 doc = self.markup(getdoc(object), self.preformat,
513 funcs, classes, mdict)
514 if doc: doc = '<small><tt>' + doc + '<br>&nbsp;</tt></small>'
515 return self.section(title, '#000000', '#ffc8d8', contents, 10, doc)
516
517 def docmethod(self, object, funcs={}, classes={}, methods={}, clname=''):
518 """Produce HTML documentation for a method object."""
519 return self.document(
520 object.im_func, funcs, classes, methods, clname)
521
522 def formatvalue(self, object):
523 """Format an argument default value as text."""
524 return ('<small><font color="#909090">=%s</font></small>' %
525 self.repr(object))
526
527 def docfunction(self, object, funcs={}, classes={}, methods={}, clname=''):
528 """Produce HTML documentation for a function object."""
529 args, varargs, varkw, defaults = inspect.getargspec(object)
530 argspec = inspect.formatargspec(
531 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
532
533 if object.__name__ == '<lambda>':
534 decl = '<em>lambda</em> ' + argspec[1:-1]
535 else:
536 anchor = clname + '-' + object.__name__
537 decl = '<a name="%s"\n><strong>%s</strong>%s</a>\n' % (
538 anchor, object.__name__, argspec)
539 doc = self.markup(getdoc(object), self.preformat,
540 funcs, classes, methods)
541 doc = replace(doc, ('<br>\n', '</tt></small\n><dd><small><tt>'))
542 doc = doc and '<tt>%s</tt>' % doc
543 return '<dl><dt>%s<dd><small>%s</small></dl>' % (decl, doc)
544
545 def docbuiltin(self, object, *extras):
546 """Produce HTML documentation for a built-in function."""
547 return '<dl><dt><strong>%s</strong>(...)</dl>' % object.__name__
548
549 def page(self, object):
550 """Produce a complete HTML page of documentation for an object."""
551 return '''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
552<html><title>Python: %s</title>
553<body bgcolor="#ffffff">
554%s
555</body></html>
556''' % (describe(object), self.document(object))
557
558 def index(self, dir, shadowed=None):
559 """Generate an HTML index for a directory of modules."""
560 modpkgs = []
561 if shadowed is None: shadowed = {}
562 seen = {}
563 files = os.listdir(dir)
564
565 def found(name, ispackage,
566 modpkgs=modpkgs, shadowed=shadowed, seen=seen):
567 if not seen.has_key(name):
568 modpkgs.append((name, '', ispackage, shadowed.has_key(name)))
569 seen[name] = 1
570 shadowed[name] = 1
571
572 # Package spam/__init__.py takes precedence over module spam.py.
573 for file in files:
574 path = os.path.join(dir, file)
575 if ispackage(path): found(file, 1)
576 for file in files:
577 path = os.path.join(dir, file)
578 if file[:1] != '_' and os.path.isfile(path):
579 modname = modulename(file)
580 if modname: found(modname, 0)
581
582 modpkgs.sort()
583 contents = self.multicolumn(modpkgs, self.modpkglink)
584 return self.bigsection(dir, '#ffffff', '#ee77aa', contents)
585
586# -------------------------------------------- text documentation generator
587
588class TextRepr(Repr):
589 """Class for safely making a text representation of a Python object."""
590 def __init__(self):
591 Repr.__init__(self)
592 self.maxlist = self.maxtuple = self.maxdict = 10
593 self.maxstring = self.maxother = 50
594
595 def repr1(self, x, level):
596 methodname = 'repr_' + join(split(type(x).__name__), '_')
597 if hasattr(self, methodname):
598 return getattr(self, methodname)(x, level)
599 else:
600 return cram(cleanid(repr(x)), self.maxother)
601
602 def repr_instance(self, x, level):
603 try:
604 return cram(cleanid(repr(x)), self.maxstring)
605 except:
606 return '<%s instance>' % x.__class__.__name__
607
608class TextDoc(Doc):
609 """Formatter class for text documentation."""
610
611 # ------------------------------------------- text formatting utilities
612
613 _repr_instance = TextRepr()
614 repr = _repr_instance.repr
615
616 def bold(self, text):
617 """Format a string in bold by overstriking."""
618 return join(map(lambda ch: ch + '\b' + ch, text), '')
619
620 def indent(self, text, prefix=' '):
621 """Indent text by prepending a given prefix to each line."""
622 if not text: return ''
623 lines = split(text, '\n')
624 lines = map(lambda line, prefix=prefix: prefix + line, lines)
625 if lines: lines[-1] = rstrip(lines[-1])
626 return join(lines, '\n')
627
628 def section(self, title, contents):
629 """Format a section with a given heading."""
630 return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n'
631
632 # ---------------------------------------------- type-specific routines
633
634 def doctree(self, tree, modname, parent=None, prefix=''):
635 """Render in text a class tree as returned by inspect.getclasstree()."""
636 result = ''
637 for entry in tree:
638 if type(entry) is type(()):
639 cl, bases = entry
640 result = result + prefix + classname(cl, modname)
641 if bases and bases != (parent,):
642 parents = map(lambda cl, m=modname: classname(cl, m), bases)
643 result = result + '(%s)' % join(parents, ', ')
644 result = result + '\n'
645 elif type(entry) is type([]):
646 result = result + self.doctree(
647 entry, modname, cl, prefix + ' ')
648 return result
649
650 def docmodule(self, object):
651 """Produce text documentation for a given module object."""
652 result = ''
653
654 name = object.__name__
655 lines = split(strip(getdoc(object)), '\n')
656 if len(lines) == 1:
657 if lines[0]: name = name + ' - ' + lines[0]
658 lines = []
659 elif len(lines) >= 2 and not rstrip(lines[1]):
660 if lines[0]: name = name + ' - ' + lines[0]
661 lines = lines[2:]
662 result = result + self.section('NAME', name)
663 try: file = inspect.getfile(object) # XXX or getsourcefile?
664 except TypeError: file = None
665 result = result + self.section('FILE', file or '(built-in)')
666 if lines:
667 result = result + self.section('DESCRIPTION', join(lines, '\n'))
668
669 classes = []
670 for key, value in inspect.getmembers(object, inspect.isclass):
671 if (inspect.getmodule(value) or object) is object:
672 classes.append(value)
673 funcs = []
674 for key, value in inspect.getmembers(object, inspect.isroutine):
675 if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
676 funcs.append(value)
677 constants = []
678 for key, value in inspect.getmembers(object, isconstant):
679 if key[:1] != '_':
680 constants.append((key, value))
681
682 if hasattr(object, '__path__'):
683 modpkgs = []
684 for file in os.listdir(object.__path__[0]):
685 if file[:1] != '_':
686 path = os.path.join(object.__path__[0], file)
687 modname = modulename(file)
688 if modname and modname not in modpkgs:
689 modpkgs.append(modname)
690 elif ispackage(path):
691 modpkgs.append(file + ' (package)')
692 modpkgs.sort()
693 result = result + self.section(
694 'PACKAGE CONTENTS', join(modpkgs, '\n'))
695
696 if classes:
697 contents = self.doctree(
698 inspect.getclasstree(classes, 1), object.__name__) + '\n'
699 for item in classes:
700 contents = contents + self.document(item) + '\n'
701 result = result + self.section('CLASSES', contents)
702
703 if funcs:
704 contents = ''
705 for item in funcs:
706 contents = contents + self.document(item) + '\n'
707 result = result + self.section('FUNCTIONS', contents)
708
709 if constants:
710 contents = ''
711 for key, value in constants:
712 line = key + ' = ' + self.repr(value)
713 chop = 70 - len(line)
714 line = self.bold(key) + ' = ' + self.repr(value)
715 if chop < 0: line = line[:chop] + '...'
716 contents = contents + line + '\n'
717 result = result + self.section('CONSTANTS', contents)
718
719 if hasattr(object, '__version__'):
720 version = str(object.__version__)
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000721 if version[:11] == '$Revision$':
722 version = version[11:-1]
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000723 result = result + self.section('VERSION', version)
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000724 if hasattr(object, '__date__'):
725 result = result + self.section('DATE', str(object.__date__))
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000726 if hasattr(object, '__author__'):
Ka-Ping Yee6f3f9a42001-02-27 22:42:36 +0000727 result = result + self.section('AUTHOR', str(object.__author__))
728 if hasattr(object, '__credits__'):
729 result = result + self.section('CREDITS', str(object.__credits__))
Ka-Ping Yeedd175342001-02-27 14:43:46 +0000730 return result
731
732 def docclass(self, object):
733 """Produce text documentation for a given class object."""
734 name = object.__name__
735 bases = object.__bases__
736
737 title = 'class ' + self.bold(name)
738 if bases:
739 parents = map(lambda c, m=object.__module__: classname(c, m), bases)
740 title = title + '(%s)' % join(parents, ', ')
741
742 doc = getdoc(object)
743 contents = doc and doc + '\n'
744 methods = map(lambda (key, value): value,
745 inspect.getmembers(object, inspect.ismethod))
746 for item in methods:
747 contents = contents + '\n' + self.document(item)
748
749 if not contents: return title + '\n'
750 return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
751
752 def docmethod(self, object):
753 """Produce text documentation for a method object."""
754 return self.document(object.im_func)
755
756 def formatvalue(self, object):
757 """Format an argument default value as text."""
758 return '=' + self.repr(object)
759
760 def docfunction(self, object):
761 """Produce text documentation for a function object."""
762 try:
763 args, varargs, varkw, defaults = inspect.getargspec(object)
764 argspec = inspect.formatargspec(
765 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
766 except TypeError:
767 argspec = '(...)'
768
769 if object.__name__ == '<lambda>':
770 decl = '<lambda> ' + argspec[1:-1]
771 else:
772 decl = self.bold(object.__name__) + argspec
773 doc = getdoc(object)
774 if doc:
775 return decl + '\n' + rstrip(self.indent(doc)) + '\n'
776 else:
777 return decl + '\n'
778
779 def docbuiltin(self, object):
780 """Produce text documentation for a built-in function object."""
781 return (self.bold(object.__name__) + '(...)\n' +
782 rstrip(self.indent(object.__doc__)) + '\n')
783
784# --------------------------------------------------------- user interfaces
785
786def pager(text):
787 """The first time this is called, determine what kind of pager to use."""
788 global pager
789 pager = getpager()
790 pager(text)
791
792def getpager():
793 """Decide what method to use for paging through text."""
794 if type(sys.stdout) is not types.FileType:
795 return plainpager
796 if not sys.stdin.isatty() or not sys.stdout.isatty():
797 return plainpager
798 if os.environ.has_key('PAGER'):
799 return lambda a: pipepager(a, os.environ['PAGER'])
800 if sys.platform in ['win', 'win32', 'nt']:
801 return lambda a: tempfilepager(a, 'more')
802 if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0:
803 return lambda a: pipepager(a, 'less')
804
805 import tempfile
806 filename = tempfile.mktemp()
807 open(filename, 'w').close()
808 try:
809 if hasattr(os, 'system') and os.system('more %s' % filename) == 0:
810 return lambda text: pipepager(text, 'more')
811 else:
812 return ttypager
813 finally:
814 os.unlink(filename)
815
816def pipepager(text, cmd):
817 """Page through text by feeding it to another program."""
818 pipe = os.popen(cmd, 'w')
819 try:
820 pipe.write(text)
821 pipe.close()
822 except IOError:
823 # Ignore broken pipes caused by quitting the pager program.
824 pass
825
826def tempfilepager(text, cmd):
827 """Page through text by invoking a program on a temporary file."""
828 import tempfile
829 filename = tempfile.mktemp()
830 file = open(filename, 'w')
831 file.write(text)
832 file.close()
833 try:
834 os.system(cmd + ' ' + filename)
835 finally:
836 os.unlink(filename)
837
838def plain(text):
839 """Remove boldface formatting from text."""
840 return re.sub('.\b', '', text)
841
842def ttypager(text):
843 """Page through text on a text terminal."""
844 lines = split(plain(text), '\n')
845 try:
846 import tty
847 fd = sys.stdin.fileno()
848 old = tty.tcgetattr(fd)
849 tty.setcbreak(fd)
850 getchar = lambda: sys.stdin.read(1)
851 except ImportError:
852 tty = None
853 getchar = lambda: sys.stdin.readline()[:-1][:1]
854
855 try:
856 r = inc = os.environ.get('LINES', 25) - 1
857 sys.stdout.write(join(lines[:inc], '\n') + '\n')
858 while lines[r:]:
859 sys.stdout.write('-- more --')
860 sys.stdout.flush()
861 c = getchar()
862
863 if c in ['q', 'Q']:
864 sys.stdout.write('\r \r')
865 break
866 elif c in ['\r', '\n']:
867 sys.stdout.write('\r \r' + lines[r] + '\n')
868 r = r + 1
869 continue
870 if c in ['b', 'B', '\x1b']:
871 r = r - inc - inc
872 if r < 0: r = 0
873 sys.stdout.write('\n' + join(lines[r:r+inc], '\n') + '\n')
874 r = r + inc
875
876 finally:
877 if tty:
878 tty.tcsetattr(fd, tty.TCSAFLUSH, old)
879
880def plainpager(text):
881 """Simply print unformatted text. This is the ultimate fallback."""
882 sys.stdout.write(plain(text))
883
884def describe(thing):
885 """Produce a short description of the given kind of thing."""
886 if inspect.ismodule(thing):
887 if thing.__name__ in sys.builtin_module_names:
888 return 'built-in module ' + thing.__name__
889 if hasattr(thing, '__path__'):
890 return 'package ' + thing.__name__
891 else:
892 return 'module ' + thing.__name__
893 if inspect.isbuiltin(thing):
894 return 'built-in function ' + thing.__name__
895 if inspect.isclass(thing):
896 return 'class ' + thing.__name__
897 if inspect.isfunction(thing):
898 return 'function ' + thing.__name__
899 if inspect.ismethod(thing):
900 return 'method ' + thing.__name__
901 return repr(thing)
902
903def locate(path):
904 """Locate an object by name (or dotted path), importing as necessary."""
905 if not path: # special case: imp.find_module('') strangely succeeds
906 return None, None
907 if type(path) is not types.StringType:
908 return None, path
909 if hasattr(__builtins__, path):
910 return None, getattr(__builtins__, path)
911 parts = split(path, '.')
912 n = 1
913 while n <= len(parts):
914 path = join(parts[:n], '.')
915 try:
916 module = __import__(path)
917 module = reload(module)
918 except:
919 # Did the error occur before or after we found the module?
920 if sys.modules.has_key(path):
921 filename = sys.modules[path].__file__
922 elif sys.exc_type is SyntaxError:
923 filename = sys.exc_value.filename
924 else:
925 # module not found, so stop looking
926 break
927 # error occurred in the imported module, so report it
928 raise DocImportError(filename, sys.exc_type, sys.exc_value)
929 try:
930 x = module
931 for p in parts[1:]:
932 x = getattr(x, p)
933 return join(parts[:-1], '.'), x
934 except AttributeError:
935 n = n + 1
936 continue
937 return None, None
938
939# --------------------------------------- interactive interpreter interface
940
941text = TextDoc()
942html = HTMLDoc()
943
944def doc(thing):
945 """Display documentation on an object (for interactive use)."""
946 if type(thing) is type(""):
947 try:
948 path, x = locate(thing)
949 except DocImportError, value:
950 print 'problem in %s - %s' % (value.filename, value.args)
951 return
952 if x:
953 thing = x
954 else:
955 print 'could not find or import %s' % repr(thing)
956 return
957
958 desc = describe(thing)
959 module = inspect.getmodule(thing)
960 if module and module is not thing:
961 desc = desc + ' in module ' + module.__name__
962 pager('Help on %s:\n\n' % desc + text.document(thing))
963
964def writedocs(path, pkgpath=''):
965 if os.path.isdir(path):
966 dir = path
967 for file in os.listdir(dir):
968 path = os.path.join(dir, file)
969 if os.path.isdir(path):
970 writedocs(path, file + '.' + pkgpath)
971 if os.path.isfile(path):
972 writedocs(path, pkgpath)
973 if os.path.isfile(path):
974 modname = modulename(path)
975 if modname:
976 writedoc(pkgpath + modname)
977
978def writedoc(key):
979 """Write HTML documentation to a file in the current directory."""
980 path, object = locate(key)
981 if object:
982 file = open(key + '.html', 'w')
983 file.write(html.page(object))
984 file.close()
985 print 'wrote', key + '.html'
986
987class Helper:
988 def __repr__(self):
989 return """To get help on a Python object, call help(object).
990To get help on a module or package, either import it before calling
991help(module) or call help('modulename')."""
992
993 def __call__(self, *args):
994 if args:
995 doc(args[0])
996 else:
997 print repr(self)
998
999help = Helper()
1000
1001def man(key):
1002 """Display documentation on an object in a form similar to man(1)."""
1003 path, object = locate(key)
1004 if object:
1005 title = 'Python Library Documentation: ' + describe(object)
1006 if path: title = title + ' in ' + path
1007 pager('\n' + title + '\n\n' + text.document(object))
1008 found = 1
1009 else:
1010 print 'could not find or import %s' % repr(key)
1011
1012def apropos(key):
1013 """Print all the one-line module summaries that contain a substring."""
1014 key = lower(key)
1015 for module in sys.builtin_module_names:
1016 desc = __import__(module).__doc__ or ''
1017 desc = split(desc, '\n')[0]
1018 if find(lower(module + ' ' + desc), key) >= 0:
1019 print module, '-', desc or '(no description)'
1020 modules = []
1021 for dir in pathdirs():
1022 for module, desc in index(dir):
1023 desc = desc or ''
1024 if module not in modules:
1025 modules.append(module)
1026 if find(lower(module + ' ' + desc), key) >= 0:
1027 desc = desc or '(no description)'
1028 if module[-9:] == '.__init__':
1029 print module[:-9], '(package) -', desc
1030 else:
1031 print module, '-', desc
1032
1033# --------------------------------------------------- web browser interface
1034
1035def serve(address, callback=None):
1036 import BaseHTTPServer, mimetools
1037
1038 # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
1039 class Message(mimetools.Message):
1040 def __init__(self, fp, seekable=1):
1041 Message = self.__class__
1042 Message.__bases__[0].__bases__[0].__init__(self, fp, seekable)
1043 self.encodingheader = self.getheader('content-transfer-encoding')
1044 self.typeheader = self.getheader('content-type')
1045 self.parsetype()
1046 self.parseplist()
1047
1048 class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1049 def send_document(self, title, contents):
1050 self.send_response(200)
1051 self.send_header('Content-Type', 'text/html')
1052 self.end_headers()
1053 self.wfile.write(
1054'''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
1055<html><title>Python: %s</title><body bgcolor="#ffffff">''' % title)
1056 self.wfile.write(contents)
1057 self.wfile.write('</body></html>')
1058
1059 def do_GET(self):
1060 path = self.path
1061 if path[-5:] == '.html': path = path[:-5]
1062 if path[:1] == '/': path = path[1:]
1063 if path and path != '.':
1064 try:
1065 p, x = locate(path)
1066 except DocImportError, value:
1067 self.send_document(path, html.escape(
1068 'problem with %s - %s' % (value.filename, value.args)))
1069 return
1070 if x:
1071 self.send_document(describe(x), html.document(x))
1072 else:
1073 self.send_document(path,
1074'There is no Python module or object named "%s".' % path)
1075 else:
1076 heading = html.heading(
1077 '<br><big><big><strong>&nbsp;'
1078 'Python: Index of Modules'
1079 '</strong></big></big>',
1080 '#ffffff', '#7799ee')
1081 builtins = []
1082 for name in sys.builtin_module_names:
1083 builtins.append('<a href="%s.html">%s</a>' % (name, name))
1084 indices = ['<p>Built-in modules: ' + join(builtins, ', ')]
1085 seen = {}
1086 for dir in pathdirs():
1087 indices.append(html.index(dir, seen))
1088 self.send_document('Index of Modules', heading + join(indices))
1089
1090 def log_message(self, *args): pass
1091
1092 class DocServer(BaseHTTPServer.HTTPServer):
1093 def __init__(self, address, callback):
1094 self.callback = callback
1095 self.base.__init__(self, address, self.handler)
1096
1097 def server_activate(self):
1098 self.base.server_activate(self)
1099 if self.callback: self.callback()
1100
1101 DocServer.base = BaseHTTPServer.HTTPServer
1102 DocServer.handler = DocHandler
1103 DocHandler.MessageClass = Message
1104 try:
1105 DocServer(address, callback).serve_forever()
1106 except KeyboardInterrupt:
1107 print 'server stopped'
1108
1109# -------------------------------------------------- command-line interface
1110
1111if __name__ == '__main__':
1112 import getopt
1113 class BadUsage: pass
1114
1115 try:
1116 opts, args = getopt.getopt(sys.argv[1:], 'k:p:w')
1117 writing = 0
1118
1119 for opt, val in opts:
1120 if opt == '-k':
1121 apropos(lower(val))
1122 break
1123 if opt == '-p':
1124 try:
1125 port = int(val)
1126 except ValueError:
1127 raise BadUsage
1128 def ready(port=port):
1129 print 'server ready at http://127.0.0.1:%d/' % port
1130 serve(('127.0.0.1', port), ready)
1131 break
1132 if opt == '-w':
1133 if not args: raise BadUsage
1134 writing = 1
1135 else:
1136 if args:
1137 for arg in args:
1138 try:
1139 if os.path.isfile(arg):
1140 arg = importfile(arg)
1141 if writing:
1142 if os.path.isdir(arg): writedocs(arg)
1143 else: writedoc(arg)
1144 else: man(arg)
1145 except DocImportError, value:
1146 print 'problem in %s - %s' % (
1147 value.filename, value.args)
1148 else:
1149 if sys.platform in ['mac', 'win', 'win32', 'nt']:
1150 # GUI platforms with threading
1151 import threading
1152 ready = threading.Event()
1153 address = ('127.0.0.1', 12346)
1154 threading.Thread(
1155 target=serve, args=(address, ready.set)).start()
1156 ready.wait()
1157 import webbrowser
1158 webbrowser.open('http://127.0.0.1:12346/')
1159 else:
1160 raise BadUsage
1161
1162 except (getopt.error, BadUsage):
1163 print """%s <name> ...
1164 Show documentation on something.
1165 <name> may be the name of a Python function, module, or package,
1166 or a dotted reference to a class or function within a module or
1167 module in a package, or the filename of a Python module to import.
1168
1169%s -k <keyword>
1170 Search for a keyword in the synopsis lines of all modules.
1171
1172%s -p <port>
1173 Start an HTTP server on the given port on the local machine.
1174
1175%s -w <module> ...
1176 Write out the HTML documentation for a module to a file.
1177
1178%s -w <moduledir>
1179 Write out the HTML documentation for all modules in the tree
1180 under a given directory to files in the current directory.
1181""" % ((sys.argv[0],) * 5)