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