Change the sense of a test in how the profiler interprets exception events.
[python/dscho.git] / Lib / pydoc.py
blob1e333398b8819ed0f39b8e6e128ac65cd43cc792
1 #!/usr/bin/env python
2 """Generate Python documentation in HTML or text for interactive use.
4 In the Python interpreter, do "from pydoc import help" to provide online
5 help. Calling help(thing) on a Python object documents the object.
7 Or, at the shell command line outside of Python:
9 Run "pydoc <name>" to show documentation on something. <name> may be
10 the name of a function, module, package, or a dotted reference to a
11 class or function within a module or module in a package. If the
12 argument contains a path segment delimiter (e.g. slash on Unix,
13 backslash on Windows) it is treated as the path to a Python source file.
15 Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
16 of all available modules.
18 Run "pydoc -p <port>" to start an HTTP server on a given port on the
19 local machine to generate documentation web pages.
21 For platforms without a command line, "pydoc -g" starts the HTTP server
22 and also pops up a little window for controlling it.
24 Run "pydoc -w <name>" to write out the HTML documentation for a module
25 to a file named "<name>.html".
26 """
28 __author__ = "Ka-Ping Yee <ping@lfw.org>"
29 __date__ = "26 February 2001"
30 __version__ = "$Revision$"
31 __credits__ = """Guido van Rossum, for an excellent programming language.
32 Tommy Burnette, the original creator of manpy.
33 Paul Prescod, for all his work on onlinehelp.
34 Richard Chamberlain, for the first implementation of textdoc.
36 Mynd you, møøse bites Kan be pretty nasti..."""
38 # Known bugs that can't be fixed here:
39 # - imp.load_module() cannot be prevented from clobbering existing
40 # loaded modules, so calling synopsis() on a binary module file
41 # changes the contents of any existing module with the same name.
42 # - If the __file__ attribute on a module is a relative path and
43 # the current directory is changed with os.chdir(), an incorrect
44 # path will be displayed.
46 import sys, imp, os, stat, re, types, inspect
47 from repr import Repr
48 from string import expandtabs, find, join, lower, split, strip, rfind, rstrip
50 # --------------------------------------------------------- common routines
52 def pathdirs():
53 """Convert sys.path into a list of absolute, existing, unique paths."""
54 dirs = []
55 normdirs = []
56 for dir in sys.path:
57 dir = os.path.abspath(dir or '.')
58 normdir = os.path.normcase(dir)
59 if normdir not in normdirs and os.path.isdir(dir):
60 dirs.append(dir)
61 normdirs.append(normdir)
62 return dirs
64 def getdoc(object):
65 """Get the doc string or comments for an object."""
66 result = inspect.getdoc(object) or inspect.getcomments(object)
67 return result and re.sub('^ *\n', '', rstrip(result)) or ''
69 def splitdoc(doc):
70 """Split a doc string into a synopsis line (if any) and the rest."""
71 lines = split(strip(doc), '\n')
72 if len(lines) == 1:
73 return lines[0], ''
74 elif len(lines) >= 2 and not rstrip(lines[1]):
75 return lines[0], join(lines[2:], '\n')
76 return '', join(lines, '\n')
78 def classname(object, modname):
79 """Get a class name and qualify it with a module name if necessary."""
80 name = object.__name__
81 if object.__module__ != modname:
82 name = object.__module__ + '.' + name
83 return name
85 def isdata(object):
86 """Check if an object is of a type that probably means it's data."""
87 return not (inspect.ismodule(object) or inspect.isclass(object) or
88 inspect.isroutine(object) or inspect.isframe(object) or
89 inspect.istraceback(object) or inspect.iscode(object))
91 def replace(text, *pairs):
92 """Do a series of global replacements on a string."""
93 while pairs:
94 text = join(split(text, pairs[0]), pairs[1])
95 pairs = pairs[2:]
96 return text
98 def cram(text, maxlen):
99 """Omit part of a string if needed to make it fit in a maximum length."""
100 if len(text) > maxlen:
101 pre = max(0, (maxlen-3)/2)
102 post = max(0, maxlen-3-pre)
103 return text[:pre] + '...' + text[len(text)-post:]
104 return text
106 def stripid(text):
107 """Remove the hexadecimal id from a Python object representation."""
108 # The behaviour of %p is implementation-dependent; we check two cases.
109 for pattern in [' at 0x[0-9a-f]{6,}>$', ' at [0-9A-F]{8,}>$']:
110 if re.search(pattern, repr(Exception)):
111 return re.sub(pattern, '>', text)
112 return text
114 def _is_some_method(object):
115 return inspect.ismethod(object) or inspect.ismethoddescriptor(object)
117 def allmethods(cl):
118 methods = {}
119 for key, value in inspect.getmembers(cl, _is_some_method):
120 methods[key] = 1
121 for base in cl.__bases__:
122 methods.update(allmethods(base)) # all your base are belong to us
123 for key in methods.keys():
124 methods[key] = getattr(cl, key)
125 return methods
127 def _split_list(s, predicate):
128 """Split sequence s via predicate, and return pair ([true], [false]).
130 The return value is a 2-tuple of lists,
131 ([x for x in s if predicate(x)],
132 [x for x in s if not predicate(x)])
135 yes = []
136 no = []
137 for x in s:
138 if predicate(x):
139 yes.append(x)
140 else:
141 no.append(x)
142 return yes, no
144 # ----------------------------------------------------- module manipulation
146 def ispackage(path):
147 """Guess whether a path refers to a package directory."""
148 if os.path.isdir(path):
149 for ext in ['.py', '.pyc', '.pyo']:
150 if os.path.isfile(os.path.join(path, '__init__' + ext)):
151 return 1
153 def synopsis(filename, cache={}):
154 """Get the one-line summary out of a module file."""
155 mtime = os.stat(filename)[stat.ST_MTIME]
156 lastupdate, result = cache.get(filename, (0, None))
157 if lastupdate < mtime:
158 info = inspect.getmoduleinfo(filename)
159 file = open(filename)
160 if info and 'b' in info[2]: # binary modules have to be imported
161 try: module = imp.load_module('__temp__', file, filename, info[1:])
162 except: return None
163 result = split(module.__doc__ or '', '\n')[0]
164 del sys.modules['__temp__']
165 else: # text modules can be directly examined
166 line = file.readline()
167 while line[:1] == '#' or not strip(line):
168 line = file.readline()
169 if not line: break
170 line = strip(line)
171 if line[:4] == 'r"""': line = line[1:]
172 if line[:3] == '"""':
173 line = line[3:]
174 if line[-1:] == '\\': line = line[:-1]
175 while not strip(line):
176 line = file.readline()
177 if not line: break
178 result = strip(split(line, '"""')[0])
179 else: result = None
180 file.close()
181 cache[filename] = (mtime, result)
182 return result
184 class ErrorDuringImport(Exception):
185 """Errors that occurred while trying to import something to document it."""
186 def __init__(self, filename, (exc, value, tb)):
187 self.filename = filename
188 self.exc = exc
189 self.value = value
190 self.tb = tb
192 def __str__(self):
193 exc = self.exc
194 if type(exc) is types.ClassType:
195 exc = exc.__name__
196 return 'problem in %s - %s: %s' % (self.filename, exc, self.value)
198 def importfile(path):
199 """Import a Python source file or compiled file given its path."""
200 magic = imp.get_magic()
201 file = open(path, 'r')
202 if file.read(len(magic)) == magic:
203 kind = imp.PY_COMPILED
204 else:
205 kind = imp.PY_SOURCE
206 file.close()
207 filename = os.path.basename(path)
208 name, ext = os.path.splitext(filename)
209 file = open(path, 'r')
210 try:
211 module = imp.load_module(name, file, path, (ext, 'r', kind))
212 except:
213 raise ErrorDuringImport(path, sys.exc_info())
214 file.close()
215 return module
217 def safeimport(path, forceload=0, cache={}):
218 """Import a module; handle errors; return None if the module isn't found.
220 If the module *is* found but an exception occurs, it's wrapped in an
221 ErrorDuringImport exception and reraised. Unlike __import__, if a
222 package path is specified, the module at the end of the path is returned,
223 not the package at the beginning. If the optional 'forceload' argument
224 is 1, we reload the module from disk (unless it's a dynamic extension)."""
225 if forceload and sys.modules.has_key(path):
226 # This is the only way to be sure. Checking the mtime of the file
227 # isn't good enough (e.g. what if the module contains a class that
228 # inherits from another module that has changed?).
229 if path not in sys.builtin_module_names:
230 # Python never loads a dynamic extension a second time from the
231 # same path, even if the file is changed or missing. Deleting
232 # the entry in sys.modules doesn't help for dynamic extensions,
233 # so we're not even going to try to keep them up to date.
234 info = inspect.getmoduleinfo(sys.modules[path].__file__)
235 if info[3] != imp.C_EXTENSION:
236 cache[path] = sys.modules[path] # prevent module from clearing
237 del sys.modules[path]
238 try:
239 module = __import__(path)
240 except:
241 # Did the error occur before or after the module was found?
242 (exc, value, tb) = info = sys.exc_info()
243 if sys.modules.has_key(path):
244 # An error occured while executing the imported module.
245 raise ErrorDuringImport(sys.modules[path].__file__, info)
246 elif exc is SyntaxError:
247 # A SyntaxError occurred before we could execute the module.
248 raise ErrorDuringImport(value.filename, info)
249 elif exc is ImportError and \
250 split(lower(str(value)))[:2] == ['no', 'module']:
251 # The module was not found.
252 return None
253 else:
254 # Some other error occurred during the importing process.
255 raise ErrorDuringImport(path, sys.exc_info())
256 for part in split(path, '.')[1:]:
257 try: module = getattr(module, part)
258 except AttributeError: return None
259 return module
261 # ---------------------------------------------------- formatter base class
263 class Doc:
264 def document(self, object, name=None, *args):
265 """Generate documentation for an object."""
266 args = (object, name) + args
267 if inspect.ismodule(object): return apply(self.docmodule, args)
268 if inspect.isclass(object): return apply(self.docclass, args)
269 if inspect.isroutine(object): return apply(self.docroutine, args)
270 return apply(self.docother, args)
272 def fail(self, object, name=None, *args):
273 """Raise an exception for unimplemented types."""
274 message = "don't know how to document object%s of type %s" % (
275 name and ' ' + repr(name), type(object).__name__)
276 raise TypeError, message
278 docmodule = docclass = docroutine = docother = fail
280 # -------------------------------------------- HTML documentation generator
282 class HTMLRepr(Repr):
283 """Class for safely making an HTML representation of a Python object."""
284 def __init__(self):
285 Repr.__init__(self)
286 self.maxlist = self.maxtuple = 20
287 self.maxdict = 10
288 self.maxstring = self.maxother = 100
290 def escape(self, text):
291 return replace(text, '&', '&amp;', '<', '&lt;', '>', '&gt;')
293 def repr(self, object):
294 return Repr.repr(self, object)
296 def repr1(self, x, level):
297 methodname = 'repr_' + join(split(type(x).__name__), '_')
298 if hasattr(self, methodname):
299 return getattr(self, methodname)(x, level)
300 else:
301 return self.escape(cram(stripid(repr(x)), self.maxother))
303 def repr_string(self, x, level):
304 test = cram(x, self.maxstring)
305 testrepr = repr(test)
306 if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
307 # Backslashes are only literal in the string and are never
308 # needed to make any special characters, so show a raw string.
309 return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
310 return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
311 r'<font color="#c040c0">\1</font>',
312 self.escape(testrepr))
314 def repr_instance(self, x, level):
315 try:
316 return self.escape(cram(stripid(repr(x)), self.maxstring))
317 except:
318 return self.escape('<%s instance>' % x.__class__.__name__)
320 repr_unicode = repr_string
322 class HTMLDoc(Doc):
323 """Formatter class for HTML documentation."""
325 # ------------------------------------------- HTML formatting utilities
327 _repr_instance = HTMLRepr()
328 repr = _repr_instance.repr
329 escape = _repr_instance.escape
331 def page(self, title, contents):
332 """Format an HTML page."""
333 return '''
334 <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
335 <html><head><title>Python: %s</title>
336 <style type="text/css"><!--
337 TT { font-family: lucidatypewriter, lucida console, courier }
338 --></style></head><body bgcolor="#f0f0f8">
340 </body></html>''' % (title, contents)
342 def heading(self, title, fgcol, bgcol, extras=''):
343 """Format a page heading."""
344 return '''
345 <table width="100%%" cellspacing=0 cellpadding=2 border=0>
346 <tr bgcolor="%s">
347 <td valign=bottom>&nbsp;<br>
348 <font color="%s" face="helvetica, arial">&nbsp;<br>%s</font></td
349 ><td align=right valign=bottom
350 ><font color="%s" face="helvetica, arial">%s</font></td></tr></table>
351 ''' % (bgcol, fgcol, title, fgcol, extras or '&nbsp;')
353 def section(self, title, fgcol, bgcol, contents, width=10,
354 prelude='', marginalia=None, gap='&nbsp;&nbsp;'):
355 """Format a section with a heading."""
356 if marginalia is None:
357 marginalia = '<tt>' + '&nbsp;' * width + '</tt>'
358 result = '''
359 <p><table width="100%%" cellspacing=0 cellpadding=2 border=0>
360 <tr bgcolor="%s">
361 <td colspan=3 valign=bottom>&nbsp;<br>
362 <font color="%s" face="helvetica, arial">%s</font></td></tr>
363 ''' % (bgcol, fgcol, title)
364 if prelude:
365 result = result + '''
366 <tr bgcolor="%s"><td rowspan=2>%s</td>
367 <td colspan=2>%s</td></tr>
368 <tr><td>%s</td>''' % (bgcol, marginalia, prelude, gap)
369 else:
370 result = result + '''
371 <tr><td bgcolor="%s">%s</td><td>%s</td>''' % (bgcol, marginalia, gap)
373 return result + '\n<td width="100%%">%s</td></tr></table>' % contents
375 def bigsection(self, title, *args):
376 """Format a section with a big heading."""
377 title = '<big><strong>%s</strong></big>' % title
378 return apply(self.section, (title,) + args)
380 def preformat(self, text):
381 """Format literal preformatted text."""
382 text = self.escape(expandtabs(text))
383 return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
384 ' ', '&nbsp;', '\n', '<br>\n')
386 def multicolumn(self, list, format, cols=4):
387 """Format a list of items into a multi-column list."""
388 result = ''
389 rows = (len(list)+cols-1)/cols
390 for col in range(cols):
391 result = result + '<td width="%d%%" valign=top>' % (100/cols)
392 for i in range(rows*col, rows*col+rows):
393 if i < len(list):
394 result = result + format(list[i]) + '<br>\n'
395 result = result + '</td>'
396 return '<table width="100%%"><tr>%s</tr></table>' % result
398 def grey(self, text): return '<font color="#909090">%s</font>' % text
400 def namelink(self, name, *dicts):
401 """Make a link for an identifier, given name-to-URL mappings."""
402 for dict in dicts:
403 if dict.has_key(name):
404 return '<a href="%s">%s</a>' % (dict[name], name)
405 return name
407 def classlink(self, object, modname):
408 """Make a link for a class."""
409 name, module = object.__name__, sys.modules.get(object.__module__)
410 if hasattr(module, name) and getattr(module, name) is object:
411 return '<a href="%s.html#%s">%s</a>' % (
412 module.__name__, name, classname(object, modname))
413 return classname(object, modname)
415 def modulelink(self, object):
416 """Make a link for a module."""
417 return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
419 def modpkglink(self, (name, path, ispackage, shadowed)):
420 """Make a link for a module or package to display in an index."""
421 if shadowed:
422 return self.grey(name)
423 if path:
424 url = '%s.%s.html' % (path, name)
425 else:
426 url = '%s.html' % name
427 if ispackage:
428 text = '<strong>%s</strong>&nbsp;(package)' % name
429 else:
430 text = name
431 return '<a href="%s">%s</a>' % (url, text)
433 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
434 """Mark up some plain text, given a context of symbols to look for.
435 Each context dictionary maps object names to anchor names."""
436 escape = escape or self.escape
437 results = []
438 here = 0
439 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
440 r'RFC[- ]?(\d+)|'
441 r'PEP[- ]?(\d+)|'
442 r'(self\.)?(\w+))\b')
443 while 1:
444 match = pattern.search(text, here)
445 if not match: break
446 start, end = match.span()
447 results.append(escape(text[here:start]))
449 all, scheme, rfc, pep, selfdot, name = match.groups()
450 if scheme:
451 results.append('<a href="%s">%s</a>' % (all, escape(all)))
452 elif rfc:
453 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
454 results.append('<a href="%s">%s</a>' % (url, escape(all)))
455 elif pep:
456 url = 'http://www.python.org/peps/pep-%04d.html' % int(pep)
457 results.append('<a href="%s">%s</a>' % (url, escape(all)))
458 elif text[end:end+1] == '(':
459 results.append(self.namelink(name, methods, funcs, classes))
460 elif selfdot:
461 results.append('self.<strong>%s</strong>' % name)
462 else:
463 results.append(self.namelink(name, classes))
464 here = end
465 results.append(escape(text[here:]))
466 return join(results, '')
468 # ---------------------------------------------- type-specific routines
470 def formattree(self, tree, modname, parent=None):
471 """Produce HTML for a class tree as given by inspect.getclasstree()."""
472 result = ''
473 for entry in tree:
474 if type(entry) is type(()):
475 c, bases = entry
476 result = result + '<dt><font face="helvetica, arial">'
477 result = result + self.classlink(c, modname)
478 if bases and bases != (parent,):
479 parents = []
480 for base in bases:
481 parents.append(self.classlink(base, modname))
482 result = result + '(' + join(parents, ', ') + ')'
483 result = result + '\n</font></dt>'
484 elif type(entry) is type([]):
485 result = result + '<dd>\n%s</dd>\n' % self.formattree(
486 entry, modname, c)
487 return '<dl>\n%s</dl>\n' % result
489 def docmodule(self, object, name=None, mod=None):
490 """Produce HTML documentation for a module object."""
491 name = object.__name__ # ignore the passed-in name
492 parts = split(name, '.')
493 links = []
494 for i in range(len(parts)-1):
495 links.append(
496 '<a href="%s.html"><font color="#ffffff">%s</font></a>' %
497 (join(parts[:i+1], '.'), parts[i]))
498 linkedname = join(links + parts[-1:], '.')
499 head = '<big><big><strong>%s</strong></big></big>' % linkedname
500 try:
501 path = inspect.getabsfile(object)
502 url = path
503 if sys.platform == 'win32':
504 import nturl2path
505 url = nturl2path.pathname2url(path)
506 filelink = '<a href="file:%s">%s</a>' % (url, path)
507 except TypeError:
508 filelink = '(built-in)'
509 info = []
510 if hasattr(object, '__version__'):
511 version = str(object.__version__)
512 if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
513 version = strip(version[11:-1])
514 info.append('version %s' % self.escape(version))
515 if hasattr(object, '__date__'):
516 info.append(self.escape(str(object.__date__)))
517 if info:
518 head = head + ' (%s)' % join(info, ', ')
519 result = self.heading(
520 head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
522 modules = inspect.getmembers(object, inspect.ismodule)
524 classes, cdict = [], {}
525 for key, value in inspect.getmembers(object, inspect.isclass):
526 if (inspect.getmodule(value) or object) is object:
527 classes.append((key, value))
528 cdict[key] = cdict[value] = '#' + key
529 for key, value in classes:
530 for base in value.__bases__:
531 key, modname = base.__name__, base.__module__
532 module = sys.modules.get(modname)
533 if modname != name and module and hasattr(module, key):
534 if getattr(module, key) is base:
535 if not cdict.has_key(key):
536 cdict[key] = cdict[base] = modname + '.html#' + key
537 funcs, fdict = [], {}
538 for key, value in inspect.getmembers(object, inspect.isroutine):
539 if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
540 funcs.append((key, value))
541 fdict[key] = '#-' + key
542 if inspect.isfunction(value): fdict[value] = fdict[key]
543 data = []
544 for key, value in inspect.getmembers(object, isdata):
545 if key not in ['__builtins__', '__doc__']:
546 data.append((key, value))
548 doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
549 doc = doc and '<tt>%s</tt>' % doc
550 result = result + '<p>%s</p>\n' % doc
552 if hasattr(object, '__path__'):
553 modpkgs = []
554 modnames = []
555 for file in os.listdir(object.__path__[0]):
556 path = os.path.join(object.__path__[0], file)
557 modname = inspect.getmodulename(file)
558 if modname and modname not in modnames:
559 modpkgs.append((modname, name, 0, 0))
560 modnames.append(modname)
561 elif ispackage(path):
562 modpkgs.append((file, name, 1, 0))
563 modpkgs.sort()
564 contents = self.multicolumn(modpkgs, self.modpkglink)
565 result = result + self.bigsection(
566 'Package Contents', '#ffffff', '#aa55cc', contents)
567 elif modules:
568 contents = self.multicolumn(
569 modules, lambda (key, value), s=self: s.modulelink(value))
570 result = result + self.bigsection(
571 'Modules', '#fffff', '#aa55cc', contents)
573 if classes:
574 classlist = map(lambda (key, value): value, classes)
575 contents = [
576 self.formattree(inspect.getclasstree(classlist, 1), name)]
577 for key, value in classes:
578 contents.append(self.document(value, key, name, fdict, cdict))
579 result = result + self.bigsection(
580 'Classes', '#ffffff', '#ee77aa', join(contents))
581 if funcs:
582 contents = []
583 for key, value in funcs:
584 contents.append(self.document(value, key, name, fdict, cdict))
585 result = result + self.bigsection(
586 'Functions', '#ffffff', '#eeaa77', join(contents))
587 if data:
588 contents = []
589 for key, value in data:
590 contents.append(self.document(value, key))
591 result = result + self.bigsection(
592 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))
593 if hasattr(object, '__author__'):
594 contents = self.markup(str(object.__author__), self.preformat)
595 result = result + self.bigsection(
596 'Author', '#ffffff', '#7799ee', contents)
597 if hasattr(object, '__credits__'):
598 contents = self.markup(str(object.__credits__), self.preformat)
599 result = result + self.bigsection(
600 'Credits', '#ffffff', '#7799ee', contents)
602 return result
604 def docclass(self, object, name=None, mod=None, funcs={}, classes={}):
605 """Produce HTML documentation for a class object."""
606 realname = object.__name__
607 name = name or realname
608 bases = object.__bases__
610 contents = []
611 push = contents.append
613 # Cute little class to pump out a horizontal rule between sections.
614 class HorizontalRule:
615 def __init__(self):
616 self.needone = 0
617 def maybe(self):
618 if self.needone:
619 push('<hr>\n')
620 self.needone = 1
621 hr = HorizontalRule()
623 # List the mro, if non-trivial.
624 mro = list(inspect.getmro(object))
625 if len(mro) > 2:
626 hr.maybe()
627 push('<dl><dt>Method resolution order:</dt>\n')
628 for base in mro:
629 push('<dd>%s</dd>\n' % self.classlink(base,
630 object.__module__))
631 push('</dl>\n')
633 def spill(msg, attrs, predicate):
634 ok, attrs = _split_list(attrs, predicate)
635 if ok:
636 hr.maybe()
637 push(msg)
638 for name, kind, homecls, value in ok:
639 push(self.document(getattr(object, name), name, mod,
640 funcs, classes, mdict, object))
641 push('\n')
642 return attrs
644 def spillproperties(msg, attrs, predicate):
645 ok, attrs = _split_list(attrs, predicate)
646 if ok:
647 hr.maybe()
648 push(msg)
649 for name, kind, homecls, value in ok:
650 push('<dl><dt><strong>%s</strong></dt>\n' % name)
651 if value.__doc__ is not None:
652 doc = self.markup(value.__doc__, self.preformat,
653 funcs, classes, mdict)
654 push('<dd><tt>%s</tt></dd>\n' % doc)
655 for attr, tag in [("fget", " getter"),
656 ("fset", " setter"),
657 ("fdel", " deleter")]:
658 func = getattr(value, attr)
659 if func is not None:
660 base = self.document(func, name + tag, mod,
661 funcs, classes, mdict, object)
662 push('<dd>%s</dd>\n' % base)
663 push('</dl>\n')
664 return attrs
666 def spilldata(msg, attrs, predicate):
667 ok, attrs = _split_list(attrs, predicate)
668 if ok:
669 hr.maybe()
670 push(msg)
671 for name, kind, homecls, value in ok:
672 base = self.docother(getattr(object, name), name, mod)
673 doc = getattr(value, "__doc__", None)
674 if doc is None:
675 push('<dl><dt>%s</dl>\n' % base)
676 else:
677 doc = self.markup(getdoc(value), self.preformat,
678 funcs, classes, mdict)
679 doc = '<dd><tt>%s</tt>' % doc
680 push('<dl><dt>%s%s</dl>\n' % (base, doc))
681 push('\n')
682 return attrs
684 attrs = inspect.classify_class_attrs(object)
685 mdict = {}
686 for key, kind, homecls, value in attrs:
687 mdict[key] = anchor = '#' + name + '-' + key
688 value = getattr(object, key)
689 try:
690 # The value may not be hashable (e.g., a data attr with
691 # a dict or list value).
692 mdict[value] = anchor
693 except TypeError:
694 pass
696 while attrs:
697 if mro:
698 thisclass = mro.pop(0)
699 else:
700 thisclass = attrs[0][2]
701 attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
703 if thisclass is object:
704 tag = "defined here"
705 else:
706 tag = "inherited from %s" % self.classlink(thisclass,
707 object.__module__)
708 tag += ':<br>\n'
710 # Sort attrs by name.
711 attrs.sort(lambda t1, t2: cmp(t1[0], t2[0]))
713 # Pump out the attrs, segregated by kind.
714 attrs = spill("Methods %s" % tag, attrs,
715 lambda t: t[1] == 'method')
716 attrs = spill("Class methods %s" % tag, attrs,
717 lambda t: t[1] == 'class method')
718 attrs = spill("Static methods %s" % tag, attrs,
719 lambda t: t[1] == 'static method')
720 attrs = spillproperties("Properties %s" % tag, attrs,
721 lambda t: t[1] == 'property')
722 attrs = spilldata("Data and non-method functions %s" % tag, attrs,
723 lambda t: t[1] == 'data')
724 assert attrs == []
725 attrs = inherited
727 contents = ''.join(contents)
729 if name == realname:
730 title = '<a name="%s">class <strong>%s</strong></a>' % (
731 name, realname)
732 else:
733 title = '<strong>%s</strong> = <a name="%s">class %s</a>' % (
734 name, name, realname)
735 if bases:
736 parents = []
737 for base in bases:
738 parents.append(self.classlink(base, object.__module__))
739 title = title + '(%s)' % join(parents, ', ')
740 doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)
741 doc = doc and '<tt>%s<br>&nbsp;</tt>' % doc or '&nbsp;'
743 return self.section(title, '#000000', '#ffc8d8', contents, 5, doc)
745 def formatvalue(self, object):
746 """Format an argument default value as text."""
747 return self.grey('=' + self.repr(object))
749 def docroutine(self, object, name=None, mod=None,
750 funcs={}, classes={}, methods={}, cl=None):
751 """Produce HTML documentation for a function or method object."""
752 realname = object.__name__
753 name = name or realname
754 anchor = (cl and cl.__name__ or '') + '-' + name
755 note = ''
756 skipdocs = 0
757 if inspect.ismethod(object):
758 imclass = object.im_class
759 if cl:
760 if imclass is not cl:
761 note = ' from ' + self.classlink(imclass, mod)
762 else:
763 if object.im_self:
764 note = ' method of %s instance' % self.classlink(
765 object.im_self.__class__, mod)
766 else:
767 note = ' unbound %s method' % self.classlink(imclass,mod)
768 object = object.im_func
770 if name == realname:
771 title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
772 else:
773 if (cl and cl.__dict__.has_key(realname) and
774 cl.__dict__[realname] is object):
775 reallink = '<a href="#%s">%s</a>' % (
776 cl.__name__ + '-' + realname, realname)
777 skipdocs = 1
778 else:
779 reallink = realname
780 title = '<a name="%s"><strong>%s</strong></a> = %s' % (
781 anchor, name, reallink)
782 if inspect.isfunction(object):
783 args, varargs, varkw, defaults = inspect.getargspec(object)
784 argspec = inspect.formatargspec(
785 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
786 if realname == '<lambda>':
787 decl = '<em>lambda</em>'
788 argspec = argspec[1:-1] # remove parentheses
789 else:
790 argspec = '(...)'
792 decl = title + argspec + (note and self.grey(
793 '<font face="helvetica, arial">%s</font>' % note))
795 if skipdocs:
796 return '<dl><dt>%s</dt></dl>\n' % decl
797 else:
798 doc = self.markup(
799 getdoc(object), self.preformat, funcs, classes, methods)
800 doc = doc and '<dd><tt>%s</tt></dd>' % doc
801 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
803 def docother(self, object, name=None, mod=None):
804 """Produce HTML documentation for a data object."""
805 lhs = name and '<strong>%s</strong> = ' % name or ''
806 return lhs + self.repr(object)
808 def index(self, dir, shadowed=None):
809 """Generate an HTML index for a directory of modules."""
810 modpkgs = []
811 if shadowed is None: shadowed = {}
812 seen = {}
813 files = os.listdir(dir)
815 def found(name, ispackage,
816 modpkgs=modpkgs, shadowed=shadowed, seen=seen):
817 if not seen.has_key(name):
818 modpkgs.append((name, '', ispackage, shadowed.has_key(name)))
819 seen[name] = 1
820 shadowed[name] = 1
822 # Package spam/__init__.py takes precedence over module spam.py.
823 for file in files:
824 path = os.path.join(dir, file)
825 if ispackage(path): found(file, 1)
826 for file in files:
827 path = os.path.join(dir, file)
828 if os.path.isfile(path):
829 modname = inspect.getmodulename(file)
830 if modname: found(modname, 0)
832 modpkgs.sort()
833 contents = self.multicolumn(modpkgs, self.modpkglink)
834 return self.bigsection(dir, '#ffffff', '#ee77aa', contents)
836 # -------------------------------------------- text documentation generator
838 class TextRepr(Repr):
839 """Class for safely making a text representation of a Python object."""
840 def __init__(self):
841 Repr.__init__(self)
842 self.maxlist = self.maxtuple = 20
843 self.maxdict = 10
844 self.maxstring = self.maxother = 100
846 def repr1(self, x, level):
847 methodname = 'repr_' + join(split(type(x).__name__), '_')
848 if hasattr(self, methodname):
849 return getattr(self, methodname)(x, level)
850 else:
851 return cram(stripid(repr(x)), self.maxother)
853 def repr_string(self, x, level):
854 test = cram(x, self.maxstring)
855 testrepr = repr(test)
856 if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
857 # Backslashes are only literal in the string and are never
858 # needed to make any special characters, so show a raw string.
859 return 'r' + testrepr[0] + test + testrepr[0]
860 return testrepr
862 def repr_instance(self, x, level):
863 try:
864 return cram(stripid(repr(x)), self.maxstring)
865 except:
866 return '<%s instance>' % x.__class__.__name__
868 class TextDoc(Doc):
869 """Formatter class for text documentation."""
871 # ------------------------------------------- text formatting utilities
873 _repr_instance = TextRepr()
874 repr = _repr_instance.repr
876 def bold(self, text):
877 """Format a string in bold by overstriking."""
878 return join(map(lambda ch: ch + '\b' + ch, text), '')
880 def indent(self, text, prefix=' '):
881 """Indent text by prepending a given prefix to each line."""
882 if not text: return ''
883 lines = split(text, '\n')
884 lines = map(lambda line, prefix=prefix: prefix + line, lines)
885 if lines: lines[-1] = rstrip(lines[-1])
886 return join(lines, '\n')
888 def section(self, title, contents):
889 """Format a section with a given heading."""
890 return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n'
892 # ---------------------------------------------- type-specific routines
894 def formattree(self, tree, modname, parent=None, prefix=''):
895 """Render in text a class tree as returned by inspect.getclasstree()."""
896 result = ''
897 for entry in tree:
898 if type(entry) is type(()):
899 c, bases = entry
900 result = result + prefix + classname(c, modname)
901 if bases and bases != (parent,):
902 parents = map(lambda c, m=modname: classname(c, m), bases)
903 result = result + '(%s)' % join(parents, ', ')
904 result = result + '\n'
905 elif type(entry) is type([]):
906 result = result + self.formattree(
907 entry, modname, c, prefix + ' ')
908 return result
910 def docmodule(self, object, name=None, mod=None):
911 """Produce text documentation for a given module object."""
912 name = object.__name__ # ignore the passed-in name
913 synop, desc = splitdoc(getdoc(object))
914 result = self.section('NAME', name + (synop and ' - ' + synop))
916 try:
917 file = inspect.getabsfile(object)
918 except TypeError:
919 file = '(built-in)'
920 result = result + self.section('FILE', file)
921 if desc:
922 result = result + self.section('DESCRIPTION', desc)
924 classes = []
925 for key, value in inspect.getmembers(object, inspect.isclass):
926 if (inspect.getmodule(value) or object) is object:
927 classes.append((key, value))
928 funcs = []
929 for key, value in inspect.getmembers(object, inspect.isroutine):
930 if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
931 funcs.append((key, value))
932 data = []
933 for key, value in inspect.getmembers(object, isdata):
934 if key not in ['__builtins__', '__doc__']:
935 data.append((key, value))
937 if hasattr(object, '__path__'):
938 modpkgs = []
939 for file in os.listdir(object.__path__[0]):
940 path = os.path.join(object.__path__[0], file)
941 modname = inspect.getmodulename(file)
942 if modname and modname not in modpkgs:
943 modpkgs.append(modname)
944 elif ispackage(path):
945 modpkgs.append(file + ' (package)')
946 modpkgs.sort()
947 result = result + self.section(
948 'PACKAGE CONTENTS', join(modpkgs, '\n'))
950 if classes:
951 classlist = map(lambda (key, value): value, classes)
952 contents = [self.formattree(
953 inspect.getclasstree(classlist, 1), name)]
954 for key, value in classes:
955 contents.append(self.document(value, key, name))
956 result = result + self.section('CLASSES', join(contents, '\n'))
958 if funcs:
959 contents = []
960 for key, value in funcs:
961 contents.append(self.document(value, key, name))
962 result = result + self.section('FUNCTIONS', join(contents, '\n'))
964 if data:
965 contents = []
966 for key, value in data:
967 contents.append(self.docother(value, key, name, 70))
968 result = result + self.section('DATA', join(contents, '\n'))
970 if hasattr(object, '__version__'):
971 version = str(object.__version__)
972 if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
973 version = strip(version[11:-1])
974 result = result + self.section('VERSION', version)
975 if hasattr(object, '__date__'):
976 result = result + self.section('DATE', str(object.__date__))
977 if hasattr(object, '__author__'):
978 result = result + self.section('AUTHOR', str(object.__author__))
979 if hasattr(object, '__credits__'):
980 result = result + self.section('CREDITS', str(object.__credits__))
981 return result
983 def docclass(self, object, name=None, mod=None):
984 """Produce text documentation for a given class object."""
985 realname = object.__name__
986 name = name or realname
987 bases = object.__bases__
989 def makename(c, m=object.__module__):
990 return classname(c, m)
992 if name == realname:
993 title = 'class ' + self.bold(realname)
994 else:
995 title = self.bold(name) + ' = class ' + realname
996 if bases:
997 parents = map(makename, bases)
998 title = title + '(%s)' % join(parents, ', ')
1000 doc = getdoc(object)
1001 contents = doc and [doc + '\n'] or []
1002 push = contents.append
1004 # List the mro, if non-trivial.
1005 mro = list(inspect.getmro(object))
1006 if len(mro) > 2:
1007 push("Method resolution order:")
1008 for base in mro:
1009 push(' ' + makename(base))
1010 push('')
1012 # Cute little class to pump out a horizontal rule between sections.
1013 class HorizontalRule:
1014 def __init__(self):
1015 self.needone = 0
1016 def maybe(self):
1017 if self.needone:
1018 push('-' * 70)
1019 self.needone = 1
1020 hr = HorizontalRule()
1022 def spill(msg, attrs, predicate):
1023 ok, attrs = _split_list(attrs, predicate)
1024 if ok:
1025 hr.maybe()
1026 push(msg)
1027 for name, kind, homecls, value in ok:
1028 push(self.document(getattr(object, name),
1029 name, mod, object))
1030 return attrs
1032 def spillproperties(msg, attrs, predicate):
1033 ok, attrs = _split_list(attrs, predicate)
1034 if ok:
1035 hr.maybe()
1036 push(msg)
1037 for name, kind, homecls, value in ok:
1038 push(name)
1039 need_blank_after_doc = 0
1040 doc = getdoc(value) or ''
1041 if doc:
1042 push(self.indent(doc))
1043 need_blank_after_doc = 1
1044 for attr, tag in [("fget", " getter"),
1045 ("fset", " setter"),
1046 ("fdel", " deleter")]:
1047 func = getattr(value, attr)
1048 if func is not None:
1049 if need_blank_after_doc:
1050 push('')
1051 need_blank_after_doc = 0
1052 base = self.docother(func, name + tag, mod, 70)
1053 push(self.indent(base))
1054 push('')
1055 return attrs
1057 def spilldata(msg, attrs, predicate):
1058 ok, attrs = _split_list(attrs, predicate)
1059 if ok:
1060 hr.maybe()
1061 push(msg)
1062 for name, kind, homecls, value in ok:
1063 doc = getattr(value, "__doc__", None)
1064 push(self.docother(getattr(object, name),
1065 name, mod, 70, doc) + '\n')
1066 return attrs
1068 attrs = inspect.classify_class_attrs(object)
1069 while attrs:
1070 if mro:
1071 thisclass = mro.pop(0)
1072 else:
1073 thisclass = attrs[0][2]
1074 attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
1076 if thisclass is object:
1077 tag = "defined here"
1078 else:
1079 tag = "inherited from %s" % classname(thisclass,
1080 object.__module__)
1082 # Sort attrs by name.
1083 attrs.sort(lambda t1, t2: cmp(t1[0], t2[0]))
1085 # Pump out the attrs, segregated by kind.
1086 attrs = spill("Methods %s:\n" % tag, attrs,
1087 lambda t: t[1] == 'method')
1088 attrs = spill("Class methods %s:\n" % tag, attrs,
1089 lambda t: t[1] == 'class method')
1090 attrs = spill("Static methods %s:\n" % tag, attrs,
1091 lambda t: t[1] == 'static method')
1092 attrs = spillproperties("Properties %s:\n" % tag, attrs,
1093 lambda t: t[1] == 'property')
1094 attrs = spilldata("Data and non-method functions %s:\n" % tag,
1095 attrs, lambda t: t[1] == 'data')
1096 assert attrs == []
1097 attrs = inherited
1099 contents = '\n'.join(contents)
1100 if not contents:
1101 return title + '\n'
1102 return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
1104 def formatvalue(self, object):
1105 """Format an argument default value as text."""
1106 return '=' + self.repr(object)
1108 def docroutine(self, object, name=None, mod=None, cl=None):
1109 """Produce text documentation for a function or method object."""
1110 realname = object.__name__
1111 name = name or realname
1112 note = ''
1113 skipdocs = 0
1114 if inspect.ismethod(object):
1115 imclass = object.im_class
1116 if cl:
1117 if imclass is not cl:
1118 note = ' from ' + classname(imclass, mod)
1119 else:
1120 if object.im_self:
1121 note = ' method of %s instance' % classname(
1122 object.im_self.__class__, mod)
1123 else:
1124 note = ' unbound %s method' % classname(imclass,mod)
1125 object = object.im_func
1127 if name == realname:
1128 title = self.bold(realname)
1129 else:
1130 if (cl and cl.__dict__.has_key(realname) and
1131 cl.__dict__[realname] is object):
1132 skipdocs = 1
1133 title = self.bold(name) + ' = ' + realname
1134 if inspect.isfunction(object):
1135 args, varargs, varkw, defaults = inspect.getargspec(object)
1136 argspec = inspect.formatargspec(
1137 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
1138 if realname == '<lambda>':
1139 title = 'lambda'
1140 argspec = argspec[1:-1] # remove parentheses
1141 else:
1142 argspec = '(...)'
1143 decl = title + argspec + note
1145 if skipdocs:
1146 return decl + '\n'
1147 else:
1148 doc = getdoc(object) or ''
1149 return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
1151 def docother(self, object, name=None, mod=None, maxlen=None, doc=None):
1152 """Produce text documentation for a data object."""
1153 repr = self.repr(object)
1154 if maxlen:
1155 line = (name and name + ' = ' or '') + repr
1156 chop = maxlen - len(line)
1157 if chop < 0: repr = repr[:chop] + '...'
1158 line = (name and self.bold(name) + ' = ' or '') + repr
1159 if doc is not None:
1160 line += '\n' + self.indent(str(doc))
1161 return line
1163 # --------------------------------------------------------- user interfaces
1165 def pager(text):
1166 """The first time this is called, determine what kind of pager to use."""
1167 global pager
1168 pager = getpager()
1169 pager(text)
1171 def getpager():
1172 """Decide what method to use for paging through text."""
1173 if type(sys.stdout) is not types.FileType:
1174 return plainpager
1175 if not sys.stdin.isatty() or not sys.stdout.isatty():
1176 return plainpager
1177 if os.environ.get('TERM') in ['dumb', 'emacs']:
1178 return plainpager
1179 if os.environ.has_key('PAGER'):
1180 if sys.platform == 'win32': # pipes completely broken in Windows
1181 return lambda text: tempfilepager(plain(text), os.environ['PAGER'])
1182 elif os.environ.get('TERM') in ['dumb', 'emacs']:
1183 return lambda text: pipepager(plain(text), os.environ['PAGER'])
1184 else:
1185 return lambda text: pipepager(text, os.environ['PAGER'])
1186 if sys.platform == 'win32':
1187 return lambda text: tempfilepager(plain(text), 'more <')
1188 if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0:
1189 return lambda text: pipepager(text, 'less')
1191 import tempfile
1192 filename = tempfile.mktemp()
1193 open(filename, 'w').close()
1194 try:
1195 if hasattr(os, 'system') and os.system('more %s' % filename) == 0:
1196 return lambda text: pipepager(text, 'more')
1197 else:
1198 return ttypager
1199 finally:
1200 os.unlink(filename)
1202 def plain(text):
1203 """Remove boldface formatting from text."""
1204 return re.sub('.\b', '', text)
1206 def pipepager(text, cmd):
1207 """Page through text by feeding it to another program."""
1208 pipe = os.popen(cmd, 'w')
1209 try:
1210 pipe.write(text)
1211 pipe.close()
1212 except IOError:
1213 pass # Ignore broken pipes caused by quitting the pager program.
1215 def tempfilepager(text, cmd):
1216 """Page through text by invoking a program on a temporary file."""
1217 import tempfile
1218 filename = tempfile.mktemp()
1219 file = open(filename, 'w')
1220 file.write(text)
1221 file.close()
1222 try:
1223 os.system(cmd + ' ' + filename)
1224 finally:
1225 os.unlink(filename)
1227 def ttypager(text):
1228 """Page through text on a text terminal."""
1229 lines = split(plain(text), '\n')
1230 try:
1231 import tty
1232 fd = sys.stdin.fileno()
1233 old = tty.tcgetattr(fd)
1234 tty.setcbreak(fd)
1235 getchar = lambda: sys.stdin.read(1)
1236 except (ImportError, AttributeError):
1237 tty = None
1238 getchar = lambda: sys.stdin.readline()[:-1][:1]
1240 try:
1241 r = inc = os.environ.get('LINES', 25) - 1
1242 sys.stdout.write(join(lines[:inc], '\n') + '\n')
1243 while lines[r:]:
1244 sys.stdout.write('-- more --')
1245 sys.stdout.flush()
1246 c = getchar()
1248 if c in ['q', 'Q']:
1249 sys.stdout.write('\r \r')
1250 break
1251 elif c in ['\r', '\n']:
1252 sys.stdout.write('\r \r' + lines[r] + '\n')
1253 r = r + 1
1254 continue
1255 if c in ['b', 'B', '\x1b']:
1256 r = r - inc - inc
1257 if r < 0: r = 0
1258 sys.stdout.write('\n' + join(lines[r:r+inc], '\n') + '\n')
1259 r = r + inc
1261 finally:
1262 if tty:
1263 tty.tcsetattr(fd, tty.TCSAFLUSH, old)
1265 def plainpager(text):
1266 """Simply print unformatted text. This is the ultimate fallback."""
1267 sys.stdout.write(plain(text))
1269 def describe(thing):
1270 """Produce a short description of the given thing."""
1271 if inspect.ismodule(thing):
1272 if thing.__name__ in sys.builtin_module_names:
1273 return 'built-in module ' + thing.__name__
1274 if hasattr(thing, '__path__'):
1275 return 'package ' + thing.__name__
1276 else:
1277 return 'module ' + thing.__name__
1278 if inspect.isbuiltin(thing):
1279 return 'built-in function ' + thing.__name__
1280 if inspect.isclass(thing):
1281 return 'class ' + thing.__name__
1282 if inspect.isfunction(thing):
1283 return 'function ' + thing.__name__
1284 if inspect.ismethod(thing):
1285 return 'method ' + thing.__name__
1286 if type(thing) is types.InstanceType:
1287 return 'instance of ' + thing.__class__.__name__
1288 return type(thing).__name__
1290 def locate(path, forceload=0):
1291 """Locate an object by name or dotted path, importing as necessary."""
1292 parts = split(path, '.')
1293 module, n = None, 0
1294 while n < len(parts):
1295 nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
1296 if nextmodule: module, n = nextmodule, n + 1
1297 else: break
1298 if module:
1299 object = module
1300 for part in parts[n:]:
1301 try: object = getattr(object, part)
1302 except AttributeError: return None
1303 return object
1304 else:
1305 import __builtin__
1306 if hasattr(__builtin__, path):
1307 return getattr(__builtin__, path)
1309 # --------------------------------------- interactive interpreter interface
1311 text = TextDoc()
1312 html = HTMLDoc()
1314 def doc(thing, title='Python Library Documentation: %s', forceload=0):
1315 """Display text documentation, given an object or a path to an object."""
1316 suffix, name = '', None
1317 if type(thing) is type(''):
1318 try:
1319 object = locate(thing, forceload)
1320 except ErrorDuringImport, value:
1321 print value
1322 return
1323 if not object:
1324 print 'no Python documentation found for %s' % repr(thing)
1325 return
1326 parts = split(thing, '.')
1327 if len(parts) > 1: suffix = ' in ' + join(parts[:-1], '.')
1328 name = parts[-1]
1329 thing = object
1331 desc = describe(thing)
1332 module = inspect.getmodule(thing)
1333 if not suffix and module and module is not thing:
1334 suffix = ' in module ' + module.__name__
1335 pager(title % (desc + suffix) + '\n\n' + text.document(thing, name))
1337 def writedoc(key, forceload=0):
1338 """Write HTML documentation to a file in the current directory."""
1339 try:
1340 object = locate(key, forceload)
1341 except ErrorDuringImport, value:
1342 print value
1343 else:
1344 if object:
1345 page = html.page(describe(object),
1346 html.document(object, object.__name__))
1347 file = open(key + '.html', 'w')
1348 file.write(page)
1349 file.close()
1350 print 'wrote', key + '.html'
1351 else:
1352 print 'no Python documentation found for %s' % repr(key)
1354 def writedocs(dir, pkgpath='', done=None):
1355 """Write out HTML documentation for all modules in a directory tree."""
1356 if done is None: done = {}
1357 for file in os.listdir(dir):
1358 path = os.path.join(dir, file)
1359 if ispackage(path):
1360 writedocs(path, pkgpath + file + '.', done)
1361 elif os.path.isfile(path):
1362 modname = inspect.getmodulename(path)
1363 if modname:
1364 modname = pkgpath + modname
1365 if not done.has_key(modname):
1366 done[modname] = 1
1367 writedoc(modname)
1369 class Helper:
1370 keywords = {
1371 'and': 'BOOLEAN',
1372 'assert': ('ref/assert', ''),
1373 'break': ('ref/break', 'while for'),
1374 'class': ('ref/class', 'CLASSES SPECIALMETHODS'),
1375 'continue': ('ref/continue', 'while for'),
1376 'def': ('ref/function', ''),
1377 'del': ('ref/del', 'BASICMETHODS'),
1378 'elif': 'if',
1379 'else': ('ref/if', 'while for'),
1380 'except': 'try',
1381 'exec': ('ref/exec', ''),
1382 'finally': 'try',
1383 'for': ('ref/for', 'break continue while'),
1384 'from': 'import',
1385 'global': ('ref/global', 'NAMESPACES'),
1386 'if': ('ref/if', 'TRUTHVALUE'),
1387 'import': ('ref/import', 'MODULES'),
1388 'in': ('ref/comparisons', 'SEQUENCEMETHODS2'),
1389 'is': 'COMPARISON',
1390 'lambda': ('ref/lambda', 'FUNCTIONS'),
1391 'not': 'BOOLEAN',
1392 'or': 'BOOLEAN',
1393 'pass': 'PASS',
1394 'print': ('ref/print', ''),
1395 'raise': ('ref/raise', 'EXCEPTIONS'),
1396 'return': ('ref/return', 'FUNCTIONS'),
1397 'try': ('ref/try', 'EXCEPTIONS'),
1398 'while': ('ref/while', 'break continue if TRUTHVALUE'),
1401 topics = {
1402 'TYPES': ('ref/types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS FUNCTIONS CLASSES MODULES FILES inspect'),
1403 'STRINGS': ('ref/strings', 'str UNICODE SEQUENCES STRINGMETHODS FORMATTING TYPES'),
1404 'STRINGMETHODS': ('lib/string-methods', 'STRINGS FORMATTING'),
1405 'FORMATTING': ('lib/typesseq-strings', 'OPERATORS'),
1406 'UNICODE': ('ref/unicode', 'encodings unicode TYPES STRING'),
1407 'NUMBERS': ('ref/numbers', 'INTEGER FLOAT COMPLEX TYPES'),
1408 'INTEGER': ('ref/integers', 'int range'),
1409 'FLOAT': ('ref/floating', 'float math'),
1410 'COMPLEX': ('ref/imaginary', 'complex cmath'),
1411 'SEQUENCES': ('lib/typesseq', 'STRINGMETHODS FORMATTING xrange LISTS'),
1412 'MAPPINGS': 'DICTIONARIES',
1413 'FUNCTIONS': ('lib/typesfunctions', 'def TYPES'),
1414 'METHODS': ('lib/typesmethods', 'class def CLASSES TYPES'),
1415 'CODEOBJECTS': ('lib/bltin-code-objects', 'compile FUNCTIONS TYPES'),
1416 'TYPEOBJECTS': ('lib/bltin-type-objects', 'types TYPES'),
1417 'FRAMEOBJECTS': 'TYPES',
1418 'TRACEBACKS': 'TYPES',
1419 'NONE': ('lib/bltin-null-object', ''),
1420 'ELLIPSIS': ('lib/bltin-ellipsis-object', 'SLICINGS'),
1421 'FILES': ('lib/bltin-file-objects', ''),
1422 'SPECIALATTRIBUTES': ('lib/specialattrs', ''),
1423 'CLASSES': ('ref/types', 'class SPECIALMETHODS PRIVATENAMES'),
1424 'MODULES': ('lib/typesmodules', 'import'),
1425 'PACKAGES': 'import',
1426 'EXPRESSIONS': ('ref/summary', 'lambda or and not in is BOOLEAN COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES LISTS DICTIONARIES BACKQUOTES'),
1427 'OPERATORS': 'EXPRESSIONS',
1428 'PRECEDENCE': 'EXPRESSIONS',
1429 'OBJECTS': ('ref/objects', 'TYPES'),
1430 'SPECIALMETHODS': ('ref/specialnames', 'BASICMETHODS ATTRIBUTEMETHODS CALLABLEMETHODS SEQUENCEMETHODS1 MAPPINGMETHODS SEQUENCEMETHODS2 NUMBERMETHODS CLASSES'),
1431 'BASICMETHODS': ('ref/customization', 'cmp hash repr str SPECIALMETHODS'),
1432 'ATTRIBUTEMETHODS': ('ref/attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
1433 'CALLABLEMETHODS': ('ref/callable-types', 'CALLS SPECIALMETHODS'),
1434 'SEQUENCEMETHODS1': ('ref/sequence-types', 'SEQUENCES SEQUENCEMETHODS2 SPECIALMETHODS'),
1435 'SEQUENCEMETHODS2': ('ref/sequence-methods', 'SEQUENCES SEQUENCEMETHODS1 SPECIALMETHODS'),
1436 'MAPPINGMETHODS': ('ref/sequence-types', 'MAPPINGS SPECIALMETHODS'),
1437 'NUMBERMETHODS': ('ref/numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT SPECIALMETHODS'),
1438 'EXECUTION': ('ref/execframes', ''),
1439 'NAMESPACES': ('ref/execframes', 'global ASSIGNMENT DELETION'),
1440 'SCOPING': 'NAMESPACES',
1441 'FRAMES': 'NAMESPACES',
1442 'EXCEPTIONS': ('ref/exceptions', 'try except finally raise'),
1443 'COERCIONS': 'CONVERSIONS',
1444 'CONVERSIONS': ('ref/conversions', ''),
1445 'IDENTIFIERS': ('ref/identifiers', 'keywords SPECIALIDENTIFIERS'),
1446 'SPECIALIDENTIFIERS': ('ref/id-classes', ''),
1447 'PRIVATENAMES': ('ref/atom-identifiers', ''),
1448 'LITERALS': ('ref/atom-literals', 'STRINGS BACKQUOTES NUMBERS TUPLELITERALS LISTLITERALS DICTIONARYLITERALS'),
1449 'TUPLES': 'SEQUENCES',
1450 'TUPLELITERALS': ('ref/exprlists', 'TUPLES LITERALS'),
1451 'LISTS': ('lib/typesseq-mutable', 'LISTLITERALS'),
1452 'LISTLITERALS': ('ref/lists', 'LISTS LITERALS'),
1453 'DICTIONARIES': ('lib/typesmapping', 'DICTIONARYLITERALS'),
1454 'DICTIONARYLITERALS': ('ref/dict', 'DICTIONARIES LITERALS'),
1455 'BACKQUOTES': ('ref/string-conversions', 'repr str STRINGS LITERALS'),
1456 'ATTRIBUTES': ('ref/attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'),
1457 'SUBSCRIPTS': ('ref/subscriptions', 'SEQUENCEMETHODS1'),
1458 'SLICINGS': ('ref/slicings', 'SEQUENCEMETHODS2'),
1459 'CALLS': ('ref/calls', 'EXPRESSIONS'),
1460 'POWER': ('ref/power', 'EXPRESSIONS'),
1461 'UNARY': ('ref/unary', 'EXPRESSIONS'),
1462 'BINARY': ('ref/binary', 'EXPRESSIONS'),
1463 'SHIFTING': ('ref/shifting', 'EXPRESSIONS'),
1464 'BITWISE': ('ref/bitwise', 'EXPRESSIONS'),
1465 'COMPARISON': ('ref/comparisons', 'EXPRESSIONS BASICMETHODS'),
1466 'BOOLEAN': ('ref/lambda', 'EXPRESSIONS TRUTHVALUE'),
1467 'ASSERTION': 'assert',
1468 'ASSIGNMENT': ('ref/assignment', 'AUGMENTEDASSIGNMENT'),
1469 'AUGMENTEDASSIGNMENT': ('ref/augassign', 'NUMBERMETHODS'),
1470 'DELETION': 'del',
1471 'PRINTING': 'print',
1472 'RETURNING': 'return',
1473 'IMPORTING': 'import',
1474 'CONDITIONAL': 'if',
1475 'LOOPING': ('ref/compound', 'for while break continue'),
1476 'TRUTHVALUE': ('lib/truth', 'if while and or not BASICMETHODS'),
1477 'DEBUGGING': ('lib/module-pdb', 'pdb'),
1480 def __init__(self, input, output):
1481 self.input = input
1482 self.output = output
1483 self.docdir = None
1484 execdir = os.path.dirname(sys.executable)
1485 homedir = os.environ.get('PYTHONHOME')
1486 for dir in [os.environ.get('PYTHONDOCS'),
1487 homedir and os.path.join(homedir, 'doc'),
1488 os.path.join(execdir, 'doc'),
1489 '/usr/doc/python-docs-' + split(sys.version)[0],
1490 '/usr/doc/python-' + split(sys.version)[0],
1491 '/usr/doc/python-docs-' + sys.version[:3],
1492 '/usr/doc/python-' + sys.version[:3]]:
1493 if dir and os.path.isdir(os.path.join(dir, 'lib')):
1494 self.docdir = dir
1496 def __repr__(self):
1497 if inspect.stack()[1][3] == '?':
1498 self()
1499 return ''
1500 return '<pydoc.Helper instance>'
1502 def __call__(self, request=None):
1503 if request is not None:
1504 self.help(request)
1505 else:
1506 self.intro()
1507 self.interact()
1508 self.output.write('''
1509 You are now leaving help and returning to the Python interpreter.
1510 If you want to ask for help on a particular object directly from the
1511 interpreter, you can type "help(object)". Executing "help('string')"
1512 has the same effect as typing a particular string at the help> prompt.
1513 ''')
1515 def interact(self):
1516 self.output.write('\n')
1517 while 1:
1518 self.output.write('help> ')
1519 self.output.flush()
1520 try:
1521 request = self.input.readline()
1522 if not request: break
1523 except KeyboardInterrupt: break
1524 request = strip(replace(request, '"', '', "'", ''))
1525 if lower(request) in ['q', 'quit']: break
1526 self.help(request)
1528 def help(self, request):
1529 if type(request) is type(''):
1530 if request == 'help': self.intro()
1531 elif request == 'keywords': self.listkeywords()
1532 elif request == 'topics': self.listtopics()
1533 elif request == 'modules': self.listmodules()
1534 elif request[:8] == 'modules ':
1535 self.listmodules(split(request)[1])
1536 elif self.keywords.has_key(request): self.showtopic(request)
1537 elif self.topics.has_key(request): self.showtopic(request)
1538 elif request: doc(request, 'Help on %s:')
1539 elif isinstance(request, Helper): self()
1540 else: doc(request, 'Help on %s:')
1541 self.output.write('\n')
1543 def intro(self):
1544 self.output.write('''
1545 Welcome to Python %s! This is the online help utility.
1547 If this is your first time using Python, you should definitely check out
1548 the tutorial on the Internet at http://www.python.org/doc/tut/.
1550 Enter the name of any module, keyword, or topic to get help on writing
1551 Python programs and using Python modules. To quit this help utility and
1552 return to the interpreter, just type "quit".
1554 To get a list of available modules, keywords, or topics, type "modules",
1555 "keywords", or "topics". Each module also comes with a one-line summary
1556 of what it does; to list the modules whose summaries contain a given word
1557 such as "spam", type "modules spam".
1558 ''' % sys.version[:3])
1560 def list(self, items, columns=4, width=80):
1561 items = items[:]
1562 items.sort()
1563 colw = width / columns
1564 rows = (len(items) + columns - 1) / columns
1565 for row in range(rows):
1566 for col in range(columns):
1567 i = col * rows + row
1568 if i < len(items):
1569 self.output.write(items[i])
1570 if col < columns - 1:
1571 self.output.write(' ' + ' ' * (colw-1 - len(items[i])))
1572 self.output.write('\n')
1574 def listkeywords(self):
1575 self.output.write('''
1576 Here is a list of the Python keywords. Enter any keyword to get more help.
1578 ''')
1579 self.list(self.keywords.keys())
1581 def listtopics(self):
1582 self.output.write('''
1583 Here is a list of available topics. Enter any topic name to get more help.
1585 ''')
1586 self.list(self.topics.keys())
1588 def showtopic(self, topic):
1589 if not self.docdir:
1590 self.output.write('''
1591 Sorry, topic and keyword documentation is not available because the Python
1592 HTML documentation files could not be found. If you have installed them,
1593 please set the environment variable PYTHONDOCS to indicate their location.
1594 ''')
1595 return
1596 target = self.topics.get(topic, self.keywords.get(topic))
1597 if not target:
1598 self.output.write('no documentation found for %s\n' % repr(topic))
1599 return
1600 if type(target) is type(''):
1601 return self.showtopic(target)
1603 filename, xrefs = target
1604 filename = self.docdir + '/' + filename + '.html'
1605 try:
1606 file = open(filename)
1607 except:
1608 self.output.write('could not read docs from %s\n' % filename)
1609 return
1611 divpat = re.compile('<div[^>]*navigat.*?</div.*?>', re.I | re.S)
1612 addrpat = re.compile('<address.*?>.*?</address.*?>', re.I | re.S)
1613 document = re.sub(addrpat, '', re.sub(divpat, '', file.read()))
1614 file.close()
1616 import htmllib, formatter, StringIO
1617 buffer = StringIO.StringIO()
1618 parser = htmllib.HTMLParser(
1619 formatter.AbstractFormatter(formatter.DumbWriter(buffer)))
1620 parser.start_table = parser.do_p
1621 parser.end_table = lambda parser=parser: parser.do_p({})
1622 parser.start_tr = parser.do_br
1623 parser.start_td = parser.start_th = lambda a, b=buffer: b.write('\t')
1624 parser.feed(document)
1625 buffer = replace(buffer.getvalue(), '\xa0', ' ', '\n', '\n ')
1626 pager(' ' + strip(buffer) + '\n')
1627 if xrefs:
1628 buffer = StringIO.StringIO()
1629 formatter.DumbWriter(buffer).send_flowing_data(
1630 'Related help topics: ' + join(split(xrefs), ', ') + '\n')
1631 self.output.write('\n%s\n' % buffer.getvalue())
1633 def listmodules(self, key=''):
1634 if key:
1635 self.output.write('''
1636 Here is a list of matching modules. Enter any module name to get more help.
1638 ''')
1639 apropos(key)
1640 else:
1641 self.output.write('''
1642 Please wait a moment while I gather a list of all available modules...
1644 ''')
1645 modules = {}
1646 def callback(path, modname, desc, modules=modules):
1647 if modname and modname[-9:] == '.__init__':
1648 modname = modname[:-9] + ' (package)'
1649 if find(modname, '.') < 0:
1650 modules[modname] = 1
1651 ModuleScanner().run(callback)
1652 self.list(modules.keys())
1653 self.output.write('''
1654 Enter any module name to get more help. Or, type "modules spam" to search
1655 for modules whose descriptions contain the word "spam".
1656 ''')
1658 help = Helper(sys.stdin, sys.stdout)
1660 class Scanner:
1661 """A generic tree iterator."""
1662 def __init__(self, roots, children, descendp):
1663 self.roots = roots[:]
1664 self.state = []
1665 self.children = children
1666 self.descendp = descendp
1668 def next(self):
1669 if not self.state:
1670 if not self.roots:
1671 return None
1672 root = self.roots.pop(0)
1673 self.state = [(root, self.children(root))]
1674 node, children = self.state[-1]
1675 if not children:
1676 self.state.pop()
1677 return self.next()
1678 child = children.pop(0)
1679 if self.descendp(child):
1680 self.state.append((child, self.children(child)))
1681 return child
1683 class ModuleScanner(Scanner):
1684 """An interruptible scanner that searches module synopses."""
1685 def __init__(self):
1686 roots = map(lambda dir: (dir, ''), pathdirs())
1687 Scanner.__init__(self, roots, self.submodules, self.isnewpackage)
1688 self.inodes = map(lambda (dir, pkg): os.stat(dir)[1], roots)
1690 def submodules(self, (dir, package)):
1691 children = []
1692 for file in os.listdir(dir):
1693 path = os.path.join(dir, file)
1694 if ispackage(path):
1695 children.append((path, package + (package and '.') + file))
1696 else:
1697 children.append((path, package))
1698 children.sort() # so that spam.py comes before spam.pyc or spam.pyo
1699 return children
1701 def isnewpackage(self, (dir, package)):
1702 inode = os.path.exists(dir) and os.stat(dir)[1]
1703 if not (os.path.islink(dir) and inode in self.inodes):
1704 self.inodes.append(inode) # detect circular symbolic links
1705 return ispackage(dir)
1707 def run(self, callback, key=None, completer=None):
1708 if key: key = lower(key)
1709 self.quit = 0
1710 seen = {}
1712 for modname in sys.builtin_module_names:
1713 if modname != '__main__':
1714 seen[modname] = 1
1715 if key is None:
1716 callback(None, modname, '')
1717 else:
1718 desc = split(__import__(modname).__doc__ or '', '\n')[0]
1719 if find(lower(modname + ' - ' + desc), key) >= 0:
1720 callback(None, modname, desc)
1722 while not self.quit:
1723 node = self.next()
1724 if not node: break
1725 path, package = node
1726 modname = inspect.getmodulename(path)
1727 if os.path.isfile(path) and modname:
1728 modname = package + (package and '.') + modname
1729 if not seen.has_key(modname):
1730 seen[modname] = 1 # if we see spam.py, skip spam.pyc
1731 if key is None:
1732 callback(path, modname, '')
1733 else:
1734 desc = synopsis(path) or ''
1735 if find(lower(modname + ' - ' + desc), key) >= 0:
1736 callback(path, modname, desc)
1737 if completer: completer()
1739 def apropos(key):
1740 """Print all the one-line module summaries that contain a substring."""
1741 def callback(path, modname, desc):
1742 if modname[-9:] == '.__init__':
1743 modname = modname[:-9] + ' (package)'
1744 print modname, desc and '- ' + desc
1745 try: import warnings
1746 except ImportError: pass
1747 else: warnings.filterwarnings('ignore') # ignore problems during import
1748 ModuleScanner().run(callback, key)
1750 # --------------------------------------------------- web browser interface
1752 def serve(port, callback=None, completer=None):
1753 import BaseHTTPServer, mimetools, select
1755 # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
1756 class Message(mimetools.Message):
1757 def __init__(self, fp, seekable=1):
1758 Message = self.__class__
1759 Message.__bases__[0].__bases__[0].__init__(self, fp, seekable)
1760 self.encodingheader = self.getheader('content-transfer-encoding')
1761 self.typeheader = self.getheader('content-type')
1762 self.parsetype()
1763 self.parseplist()
1765 class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1766 def send_document(self, title, contents):
1767 try:
1768 self.send_response(200)
1769 self.send_header('Content-Type', 'text/html')
1770 self.end_headers()
1771 self.wfile.write(html.page(title, contents))
1772 except IOError: pass
1774 def do_GET(self):
1775 path = self.path
1776 if path[-5:] == '.html': path = path[:-5]
1777 if path[:1] == '/': path = path[1:]
1778 if path and path != '.':
1779 try:
1780 obj = locate(path, forceload=1)
1781 except ErrorDuringImport, value:
1782 self.send_document(path, html.escape(str(value)))
1783 return
1784 if obj:
1785 self.send_document(describe(obj), html.document(obj, path))
1786 else:
1787 self.send_document(path,
1788 'no Python documentation found for %s' % repr(path))
1789 else:
1790 heading = html.heading(
1791 '<big><big><strong>Python: Index of Modules</strong></big></big>',
1792 '#ffffff', '#7799ee')
1793 def bltinlink(name):
1794 return '<a href="%s.html">%s</a>' % (name, name)
1795 names = filter(lambda x: x != '__main__',
1796 sys.builtin_module_names)
1797 contents = html.multicolumn(names, bltinlink)
1798 indices = ['<p>' + html.bigsection(
1799 'Built-in Modules', '#ffffff', '#ee77aa', contents)]
1801 seen = {}
1802 for dir in pathdirs():
1803 indices.append(html.index(dir, seen))
1804 contents = heading + join(indices) + '''<p align=right>
1805 <font color="#909090" face="helvetica, arial"><strong>
1806 pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font>'''
1807 self.send_document('Index of Modules', contents)
1809 def log_message(self, *args): pass
1811 class DocServer(BaseHTTPServer.HTTPServer):
1812 def __init__(self, port, callback):
1813 host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
1814 self.address = ('', port)
1815 self.url = 'http://%s:%d/' % (host, port)
1816 self.callback = callback
1817 self.base.__init__(self, self.address, self.handler)
1819 def serve_until_quit(self):
1820 import select
1821 self.quit = 0
1822 while not self.quit:
1823 rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
1824 if rd: self.handle_request()
1826 def server_activate(self):
1827 self.base.server_activate(self)
1828 if self.callback: self.callback(self)
1830 DocServer.base = BaseHTTPServer.HTTPServer
1831 DocServer.handler = DocHandler
1832 DocHandler.MessageClass = Message
1833 try:
1834 try:
1835 DocServer(port, callback).serve_until_quit()
1836 except (KeyboardInterrupt, select.error):
1837 pass
1838 finally:
1839 if completer: completer()
1841 # ----------------------------------------------------- graphical interface
1843 def gui():
1844 """Graphical interface (starts web server and pops up a control window)."""
1845 class GUI:
1846 def __init__(self, window, port=7464):
1847 self.window = window
1848 self.server = None
1849 self.scanner = None
1851 import Tkinter
1852 self.server_frm = Tkinter.Frame(window)
1853 self.title_lbl = Tkinter.Label(self.server_frm,
1854 text='Starting server...\n ')
1855 self.open_btn = Tkinter.Button(self.server_frm,
1856 text='open browser', command=self.open, state='disabled')
1857 self.quit_btn = Tkinter.Button(self.server_frm,
1858 text='quit serving', command=self.quit, state='disabled')
1860 self.search_frm = Tkinter.Frame(window)
1861 self.search_lbl = Tkinter.Label(self.search_frm, text='Search for')
1862 self.search_ent = Tkinter.Entry(self.search_frm)
1863 self.search_ent.bind('<Return>', self.search)
1864 self.stop_btn = Tkinter.Button(self.search_frm,
1865 text='stop', pady=0, command=self.stop, state='disabled')
1866 if sys.platform == 'win32':
1867 # Trying to hide and show this button crashes under Windows.
1868 self.stop_btn.pack(side='right')
1870 self.window.title('pydoc')
1871 self.window.protocol('WM_DELETE_WINDOW', self.quit)
1872 self.title_lbl.pack(side='top', fill='x')
1873 self.open_btn.pack(side='left', fill='x', expand=1)
1874 self.quit_btn.pack(side='right', fill='x', expand=1)
1875 self.server_frm.pack(side='top', fill='x')
1877 self.search_lbl.pack(side='left')
1878 self.search_ent.pack(side='right', fill='x', expand=1)
1879 self.search_frm.pack(side='top', fill='x')
1880 self.search_ent.focus_set()
1882 font = ('helvetica', sys.platform == 'win32' and 8 or 10)
1883 self.result_lst = Tkinter.Listbox(window, font=font, height=6)
1884 self.result_lst.bind('<Button-1>', self.select)
1885 self.result_lst.bind('<Double-Button-1>', self.goto)
1886 self.result_scr = Tkinter.Scrollbar(window,
1887 orient='vertical', command=self.result_lst.yview)
1888 self.result_lst.config(yscrollcommand=self.result_scr.set)
1890 self.result_frm = Tkinter.Frame(window)
1891 self.goto_btn = Tkinter.Button(self.result_frm,
1892 text='go to selected', command=self.goto)
1893 self.hide_btn = Tkinter.Button(self.result_frm,
1894 text='hide results', command=self.hide)
1895 self.goto_btn.pack(side='left', fill='x', expand=1)
1896 self.hide_btn.pack(side='right', fill='x', expand=1)
1898 self.window.update()
1899 self.minwidth = self.window.winfo_width()
1900 self.minheight = self.window.winfo_height()
1901 self.bigminheight = (self.server_frm.winfo_reqheight() +
1902 self.search_frm.winfo_reqheight() +
1903 self.result_lst.winfo_reqheight() +
1904 self.result_frm.winfo_reqheight())
1905 self.bigwidth, self.bigheight = self.minwidth, self.bigminheight
1906 self.expanded = 0
1907 self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
1908 self.window.wm_minsize(self.minwidth, self.minheight)
1910 import threading
1911 threading.Thread(
1912 target=serve, args=(port, self.ready, self.quit)).start()
1914 def ready(self, server):
1915 self.server = server
1916 self.title_lbl.config(
1917 text='Python documentation server at\n' + server.url)
1918 self.open_btn.config(state='normal')
1919 self.quit_btn.config(state='normal')
1921 def open(self, event=None, url=None):
1922 url = url or self.server.url
1923 try:
1924 import webbrowser
1925 webbrowser.open(url)
1926 except ImportError: # pre-webbrowser.py compatibility
1927 if sys.platform == 'win32':
1928 os.system('start "%s"' % url)
1929 elif sys.platform == 'mac':
1930 try: import ic
1931 except ImportError: pass
1932 else: ic.launchurl(url)
1933 else:
1934 rc = os.system('netscape -remote "openURL(%s)" &' % url)
1935 if rc: os.system('netscape "%s" &' % url)
1937 def quit(self, event=None):
1938 if self.server:
1939 self.server.quit = 1
1940 self.window.quit()
1942 def search(self, event=None):
1943 key = self.search_ent.get()
1944 self.stop_btn.pack(side='right')
1945 self.stop_btn.config(state='normal')
1946 self.search_lbl.config(text='Searching for "%s"...' % key)
1947 self.search_ent.forget()
1948 self.search_lbl.pack(side='left')
1949 self.result_lst.delete(0, 'end')
1950 self.goto_btn.config(state='disabled')
1951 self.expand()
1953 import threading
1954 if self.scanner:
1955 self.scanner.quit = 1
1956 self.scanner = ModuleScanner()
1957 threading.Thread(target=self.scanner.run,
1958 args=(self.update, key, self.done)).start()
1960 def update(self, path, modname, desc):
1961 if modname[-9:] == '.__init__':
1962 modname = modname[:-9] + ' (package)'
1963 self.result_lst.insert('end',
1964 modname + ' - ' + (desc or '(no description)'))
1966 def stop(self, event=None):
1967 if self.scanner:
1968 self.scanner.quit = 1
1969 self.scanner = None
1971 def done(self):
1972 self.scanner = None
1973 self.search_lbl.config(text='Search for')
1974 self.search_lbl.pack(side='left')
1975 self.search_ent.pack(side='right', fill='x', expand=1)
1976 if sys.platform != 'win32': self.stop_btn.forget()
1977 self.stop_btn.config(state='disabled')
1979 def select(self, event=None):
1980 self.goto_btn.config(state='normal')
1982 def goto(self, event=None):
1983 selection = self.result_lst.curselection()
1984 if selection:
1985 modname = split(self.result_lst.get(selection[0]))[0]
1986 self.open(url=self.server.url + modname + '.html')
1988 def collapse(self):
1989 if not self.expanded: return
1990 self.result_frm.forget()
1991 self.result_scr.forget()
1992 self.result_lst.forget()
1993 self.bigwidth = self.window.winfo_width()
1994 self.bigheight = self.window.winfo_height()
1995 self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
1996 self.window.wm_minsize(self.minwidth, self.minheight)
1997 self.expanded = 0
1999 def expand(self):
2000 if self.expanded: return
2001 self.result_frm.pack(side='bottom', fill='x')
2002 self.result_scr.pack(side='right', fill='y')
2003 self.result_lst.pack(side='top', fill='both', expand=1)
2004 self.window.wm_geometry('%dx%d' % (self.bigwidth, self.bigheight))
2005 self.window.wm_minsize(self.minwidth, self.bigminheight)
2006 self.expanded = 1
2008 def hide(self, event=None):
2009 self.stop()
2010 self.collapse()
2012 import Tkinter
2013 try:
2014 gui = GUI(Tkinter.Tk())
2015 Tkinter.mainloop()
2016 except KeyboardInterrupt:
2017 pass
2019 # -------------------------------------------------- command-line interface
2021 def ispath(x):
2022 return type(x) is types.StringType and find(x, os.sep) >= 0
2024 def cli():
2025 """Command-line interface (looks at sys.argv to decide what to do)."""
2026 import getopt
2027 class BadUsage: pass
2029 # Scripts don't get the current directory in their path by default.
2030 scriptdir = os.path.dirname(sys.argv[0])
2031 if scriptdir in sys.path:
2032 sys.path.remove(scriptdir)
2033 sys.path.insert(0, '.')
2035 try:
2036 opts, args = getopt.getopt(sys.argv[1:], 'gk:p:w')
2037 writing = 0
2039 for opt, val in opts:
2040 if opt == '-g':
2041 gui()
2042 return
2043 if opt == '-k':
2044 apropos(val)
2045 return
2046 if opt == '-p':
2047 try:
2048 port = int(val)
2049 except ValueError:
2050 raise BadUsage
2051 def ready(server):
2052 print 'pydoc server ready at %s' % server.url
2053 def stopped():
2054 print 'pydoc server stopped'
2055 serve(port, ready, stopped)
2056 return
2057 if opt == '-w':
2058 writing = 1
2060 if not args: raise BadUsage
2061 for arg in args:
2062 try:
2063 if ispath(arg) and os.path.isfile(arg):
2064 arg = importfile(arg)
2065 if writing:
2066 if ispath(arg) and os.path.isdir(arg):
2067 writedocs(arg)
2068 else:
2069 writedoc(arg)
2070 else:
2071 doc(arg)
2072 except ErrorDuringImport, value:
2073 print value
2075 except (getopt.error, BadUsage):
2076 cmd = sys.argv[0]
2077 print """pydoc - the Python documentation tool
2079 %s <name> ...
2080 Show text documentation on something. <name> may be the name of a
2081 function, module, or package, or a dotted reference to a class or
2082 function within a module or module in a package. If <name> contains
2083 a '%s', it is used as the path to a Python source file to document.
2085 %s -k <keyword>
2086 Search for a keyword in the synopsis lines of all available modules.
2088 %s -p <port>
2089 Start an HTTP server on the given port on the local machine.
2091 %s -g
2092 Pop up a graphical interface for finding and serving documentation.
2094 %s -w <name> ...
2095 Write out the HTML documentation for a module to a file in the current
2096 directory. If <name> contains a '%s', it is treated as a filename; if
2097 it names a directory, documentation is written for all the contents.
2098 """ % (cmd, os.sep, cmd, cmd, cmd, cmd, os.sep)
2100 if __name__ == '__main__': cli()