This commit was manufactured by cvs2svn to create tag
[python/dscho.git] / Lib / pydoc.py
blobab11d00e450eb709a32134525cf6d59e3eb13c05
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 summary="heading">
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 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
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%%" summary="list"><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, *ignored):
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 *ignored):
606 """Produce HTML documentation for a class object."""
607 realname = object.__name__
608 name = name or realname
609 bases = object.__bases__
611 contents = []
612 push = contents.append
614 # Cute little class to pump out a horizontal rule between sections.
615 class HorizontalRule:
616 def __init__(self):
617 self.needone = 0
618 def maybe(self):
619 if self.needone:
620 push('<hr>\n')
621 self.needone = 1
622 hr = HorizontalRule()
624 # List the mro, if non-trivial.
625 mro = list(inspect.getmro(object))
626 if len(mro) > 2:
627 hr.maybe()
628 push('<dl><dt>Method resolution order:</dt>\n')
629 for base in mro:
630 push('<dd>%s</dd>\n' % self.classlink(base,
631 object.__module__))
632 push('</dl>\n')
634 def spill(msg, attrs, predicate):
635 ok, attrs = _split_list(attrs, predicate)
636 if ok:
637 hr.maybe()
638 push(msg)
639 for name, kind, homecls, value in ok:
640 push(self.document(getattr(object, name), name, mod,
641 funcs, classes, mdict, object))
642 push('\n')
643 return attrs
645 def spillproperties(msg, attrs, predicate):
646 ok, attrs = _split_list(attrs, predicate)
647 if ok:
648 hr.maybe()
649 push(msg)
650 for name, kind, homecls, value in ok:
651 push('<dl><dt><strong>%s</strong></dt>\n' % name)
652 if value.__doc__ is not None:
653 doc = self.markup(value.__doc__, self.preformat,
654 funcs, classes, mdict)
655 push('<dd><tt>%s</tt></dd>\n' % doc)
656 for attr, tag in [("fget", " getter"),
657 ("fset", " setter"),
658 ("fdel", " deleter")]:
659 func = getattr(value, attr)
660 if func is not None:
661 base = self.document(func, name + tag, mod,
662 funcs, classes, mdict, object)
663 push('<dd>%s</dd>\n' % base)
664 push('</dl>\n')
665 return attrs
667 def spilldata(msg, attrs, predicate):
668 ok, attrs = _split_list(attrs, predicate)
669 if ok:
670 hr.maybe()
671 push(msg)
672 for name, kind, homecls, value in ok:
673 base = self.docother(getattr(object, name), name, mod)
674 doc = getattr(value, "__doc__", None)
675 if doc is None:
676 push('<dl><dt>%s</dl>\n' % base)
677 else:
678 doc = self.markup(getdoc(value), self.preformat,
679 funcs, classes, mdict)
680 doc = '<dd><tt>%s</tt>' % doc
681 push('<dl><dt>%s%s</dl>\n' % (base, doc))
682 push('\n')
683 return attrs
685 attrs = inspect.classify_class_attrs(object)
686 mdict = {}
687 for key, kind, homecls, value in attrs:
688 mdict[key] = anchor = '#' + name + '-' + key
689 value = getattr(object, key)
690 try:
691 # The value may not be hashable (e.g., a data attr with
692 # a dict or list value).
693 mdict[value] = anchor
694 except TypeError:
695 pass
697 while attrs:
698 if mro:
699 thisclass = mro.pop(0)
700 else:
701 thisclass = attrs[0][2]
702 attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
704 if thisclass is object:
705 tag = "defined here"
706 else:
707 tag = "inherited from %s" % self.classlink(thisclass,
708 object.__module__)
709 tag += ':<br>\n'
711 # Sort attrs by name.
712 attrs.sort(lambda t1, t2: cmp(t1[0], t2[0]))
714 # Pump out the attrs, segregated by kind.
715 attrs = spill("Methods %s" % tag, attrs,
716 lambda t: t[1] == 'method')
717 attrs = spill("Class methods %s" % tag, attrs,
718 lambda t: t[1] == 'class method')
719 attrs = spill("Static methods %s" % tag, attrs,
720 lambda t: t[1] == 'static method')
721 attrs = spillproperties("Properties %s" % tag, attrs,
722 lambda t: t[1] == 'property')
723 attrs = spilldata("Data and non-method functions %s" % tag, attrs,
724 lambda t: t[1] == 'data')
725 assert attrs == []
726 attrs = inherited
728 contents = ''.join(contents)
730 if name == realname:
731 title = '<a name="%s">class <strong>%s</strong></a>' % (
732 name, realname)
733 else:
734 title = '<strong>%s</strong> = <a name="%s">class %s</a>' % (
735 name, name, realname)
736 if bases:
737 parents = []
738 for base in bases:
739 parents.append(self.classlink(base, object.__module__))
740 title = title + '(%s)' % join(parents, ', ')
741 doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)
742 doc = doc and '<tt>%s<br>&nbsp;</tt>' % doc or '&nbsp;'
744 return self.section(title, '#000000', '#ffc8d8', contents, 5, doc)
746 def formatvalue(self, object):
747 """Format an argument default value as text."""
748 return self.grey('=' + self.repr(object))
750 def docroutine(self, object, name=None, mod=None,
751 funcs={}, classes={}, methods={}, cl=None):
752 """Produce HTML documentation for a function or method object."""
753 realname = object.__name__
754 name = name or realname
755 anchor = (cl and cl.__name__ or '') + '-' + name
756 note = ''
757 skipdocs = 0
758 if inspect.ismethod(object):
759 imclass = object.im_class
760 if cl:
761 if imclass is not cl:
762 note = ' from ' + self.classlink(imclass, mod)
763 else:
764 if object.im_self:
765 note = ' method of %s instance' % self.classlink(
766 object.im_self.__class__, mod)
767 else:
768 note = ' unbound %s method' % self.classlink(imclass,mod)
769 object = object.im_func
771 if name == realname:
772 title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
773 else:
774 if (cl and cl.__dict__.has_key(realname) and
775 cl.__dict__[realname] is object):
776 reallink = '<a href="#%s">%s</a>' % (
777 cl.__name__ + '-' + realname, realname)
778 skipdocs = 1
779 else:
780 reallink = realname
781 title = '<a name="%s"><strong>%s</strong></a> = %s' % (
782 anchor, name, reallink)
783 if inspect.isfunction(object):
784 args, varargs, varkw, defaults = inspect.getargspec(object)
785 argspec = inspect.formatargspec(
786 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
787 if realname == '<lambda>':
788 title = '<strong>%s</strong> <em>lambda</em> ' % name
789 argspec = argspec[1:-1] # remove parentheses
790 else:
791 argspec = '(...)'
793 decl = title + argspec + (note and self.grey(
794 '<font face="helvetica, arial">%s</font>' % note))
796 if skipdocs:
797 return '<dl><dt>%s</dt></dl>\n' % decl
798 else:
799 doc = self.markup(
800 getdoc(object), self.preformat, funcs, classes, methods)
801 doc = doc and '<dd><tt>%s</tt></dd>' % doc
802 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
804 def docother(self, object, name=None, mod=None, *ignored):
805 """Produce HTML documentation for a data object."""
806 lhs = name and '<strong>%s</strong> = ' % name or ''
807 return lhs + self.repr(object)
809 def index(self, dir, shadowed=None):
810 """Generate an HTML index for a directory of modules."""
811 modpkgs = []
812 if shadowed is None: shadowed = {}
813 seen = {}
814 files = os.listdir(dir)
816 def found(name, ispackage,
817 modpkgs=modpkgs, shadowed=shadowed, seen=seen):
818 if not seen.has_key(name):
819 modpkgs.append((name, '', ispackage, shadowed.has_key(name)))
820 seen[name] = 1
821 shadowed[name] = 1
823 # Package spam/__init__.py takes precedence over module spam.py.
824 for file in files:
825 path = os.path.join(dir, file)
826 if ispackage(path): found(file, 1)
827 for file in files:
828 path = os.path.join(dir, file)
829 if os.path.isfile(path):
830 modname = inspect.getmodulename(file)
831 if modname: found(modname, 0)
833 modpkgs.sort()
834 contents = self.multicolumn(modpkgs, self.modpkglink)
835 return self.bigsection(dir, '#ffffff', '#ee77aa', contents)
837 # -------------------------------------------- text documentation generator
839 class TextRepr(Repr):
840 """Class for safely making a text representation of a Python object."""
841 def __init__(self):
842 Repr.__init__(self)
843 self.maxlist = self.maxtuple = 20
844 self.maxdict = 10
845 self.maxstring = self.maxother = 100
847 def repr1(self, x, level):
848 methodname = 'repr_' + join(split(type(x).__name__), '_')
849 if hasattr(self, methodname):
850 return getattr(self, methodname)(x, level)
851 else:
852 return cram(stripid(repr(x)), self.maxother)
854 def repr_string(self, x, level):
855 test = cram(x, self.maxstring)
856 testrepr = repr(test)
857 if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
858 # Backslashes are only literal in the string and are never
859 # needed to make any special characters, so show a raw string.
860 return 'r' + testrepr[0] + test + testrepr[0]
861 return testrepr
863 def repr_instance(self, x, level):
864 try:
865 return cram(stripid(repr(x)), self.maxstring)
866 except:
867 return '<%s instance>' % x.__class__.__name__
869 class TextDoc(Doc):
870 """Formatter class for text documentation."""
872 # ------------------------------------------- text formatting utilities
874 _repr_instance = TextRepr()
875 repr = _repr_instance.repr
877 def bold(self, text):
878 """Format a string in bold by overstriking."""
879 return join(map(lambda ch: ch + '\b' + ch, text), '')
881 def indent(self, text, prefix=' '):
882 """Indent text by prepending a given prefix to each line."""
883 if not text: return ''
884 lines = split(text, '\n')
885 lines = map(lambda line, prefix=prefix: prefix + line, lines)
886 if lines: lines[-1] = rstrip(lines[-1])
887 return join(lines, '\n')
889 def section(self, title, contents):
890 """Format a section with a given heading."""
891 return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n'
893 # ---------------------------------------------- type-specific routines
895 def formattree(self, tree, modname, parent=None, prefix=''):
896 """Render in text a class tree as returned by inspect.getclasstree()."""
897 result = ''
898 for entry in tree:
899 if type(entry) is type(()):
900 c, bases = entry
901 result = result + prefix + classname(c, modname)
902 if bases and bases != (parent,):
903 parents = map(lambda c, m=modname: classname(c, m), bases)
904 result = result + '(%s)' % join(parents, ', ')
905 result = result + '\n'
906 elif type(entry) is type([]):
907 result = result + self.formattree(
908 entry, modname, c, prefix + ' ')
909 return result
911 def docmodule(self, object, name=None, mod=None):
912 """Produce text documentation for a given module object."""
913 name = object.__name__ # ignore the passed-in name
914 synop, desc = splitdoc(getdoc(object))
915 result = self.section('NAME', name + (synop and ' - ' + synop))
917 try:
918 file = inspect.getabsfile(object)
919 except TypeError:
920 file = '(built-in)'
921 result = result + self.section('FILE', file)
922 if desc:
923 result = result + self.section('DESCRIPTION', desc)
925 classes = []
926 for key, value in inspect.getmembers(object, inspect.isclass):
927 if (inspect.getmodule(value) or object) is object:
928 classes.append((key, value))
929 funcs = []
930 for key, value in inspect.getmembers(object, inspect.isroutine):
931 if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
932 funcs.append((key, value))
933 data = []
934 for key, value in inspect.getmembers(object, isdata):
935 if key not in ['__builtins__', '__doc__']:
936 data.append((key, value))
938 if hasattr(object, '__path__'):
939 modpkgs = []
940 for file in os.listdir(object.__path__[0]):
941 path = os.path.join(object.__path__[0], file)
942 modname = inspect.getmodulename(file)
943 if modname and modname not in modpkgs:
944 modpkgs.append(modname)
945 elif ispackage(path):
946 modpkgs.append(file + ' (package)')
947 modpkgs.sort()
948 result = result + self.section(
949 'PACKAGE CONTENTS', join(modpkgs, '\n'))
951 if classes:
952 classlist = map(lambda (key, value): value, classes)
953 contents = [self.formattree(
954 inspect.getclasstree(classlist, 1), name)]
955 for key, value in classes:
956 contents.append(self.document(value, key, name))
957 result = result + self.section('CLASSES', join(contents, '\n'))
959 if funcs:
960 contents = []
961 for key, value in funcs:
962 contents.append(self.document(value, key, name))
963 result = result + self.section('FUNCTIONS', join(contents, '\n'))
965 if data:
966 contents = []
967 for key, value in data:
968 contents.append(self.docother(value, key, name, 70))
969 result = result + self.section('DATA', join(contents, '\n'))
971 if hasattr(object, '__version__'):
972 version = str(object.__version__)
973 if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
974 version = strip(version[11:-1])
975 result = result + self.section('VERSION', version)
976 if hasattr(object, '__date__'):
977 result = result + self.section('DATE', str(object.__date__))
978 if hasattr(object, '__author__'):
979 result = result + self.section('AUTHOR', str(object.__author__))
980 if hasattr(object, '__credits__'):
981 result = result + self.section('CREDITS', str(object.__credits__))
982 return result
984 def docclass(self, object, name=None, mod=None):
985 """Produce text documentation for a given class object."""
986 realname = object.__name__
987 name = name or realname
988 bases = object.__bases__
990 def makename(c, m=object.__module__):
991 return classname(c, m)
993 if name == realname:
994 title = 'class ' + self.bold(realname)
995 else:
996 title = self.bold(name) + ' = class ' + realname
997 if bases:
998 parents = map(makename, bases)
999 title = title + '(%s)' % join(parents, ', ')
1001 doc = getdoc(object)
1002 contents = doc and [doc + '\n'] or []
1003 push = contents.append
1005 # List the mro, if non-trivial.
1006 mro = list(inspect.getmro(object))
1007 if len(mro) > 2:
1008 push("Method resolution order:")
1009 for base in mro:
1010 push(' ' + makename(base))
1011 push('')
1013 # Cute little class to pump out a horizontal rule between sections.
1014 class HorizontalRule:
1015 def __init__(self):
1016 self.needone = 0
1017 def maybe(self):
1018 if self.needone:
1019 push('-' * 70)
1020 self.needone = 1
1021 hr = HorizontalRule()
1023 def spill(msg, attrs, predicate):
1024 ok, attrs = _split_list(attrs, predicate)
1025 if ok:
1026 hr.maybe()
1027 push(msg)
1028 for name, kind, homecls, value in ok:
1029 push(self.document(getattr(object, name),
1030 name, mod, object))
1031 return attrs
1033 def spillproperties(msg, attrs, predicate):
1034 ok, attrs = _split_list(attrs, predicate)
1035 if ok:
1036 hr.maybe()
1037 push(msg)
1038 for name, kind, homecls, value in ok:
1039 push(name)
1040 need_blank_after_doc = 0
1041 doc = getdoc(value) or ''
1042 if doc:
1043 push(self.indent(doc))
1044 need_blank_after_doc = 1
1045 for attr, tag in [("fget", " getter"),
1046 ("fset", " setter"),
1047 ("fdel", " deleter")]:
1048 func = getattr(value, attr)
1049 if func is not None:
1050 if need_blank_after_doc:
1051 push('')
1052 need_blank_after_doc = 0
1053 base = self.docother(func, name + tag, mod, 70)
1054 push(self.indent(base))
1055 push('')
1056 return attrs
1058 def spilldata(msg, attrs, predicate):
1059 ok, attrs = _split_list(attrs, predicate)
1060 if ok:
1061 hr.maybe()
1062 push(msg)
1063 for name, kind, homecls, value in ok:
1064 doc = getattr(value, "__doc__", None)
1065 push(self.docother(getattr(object, name),
1066 name, mod, 70, doc) + '\n')
1067 return attrs
1069 attrs = inspect.classify_class_attrs(object)
1070 while attrs:
1071 if mro:
1072 thisclass = mro.pop(0)
1073 else:
1074 thisclass = attrs[0][2]
1075 attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
1077 if thisclass is object:
1078 tag = "defined here"
1079 else:
1080 tag = "inherited from %s" % classname(thisclass,
1081 object.__module__)
1083 # Sort attrs by name.
1084 attrs.sort(lambda t1, t2: cmp(t1[0], t2[0]))
1086 # Pump out the attrs, segregated by kind.
1087 attrs = spill("Methods %s:\n" % tag, attrs,
1088 lambda t: t[1] == 'method')
1089 attrs = spill("Class methods %s:\n" % tag, attrs,
1090 lambda t: t[1] == 'class method')
1091 attrs = spill("Static methods %s:\n" % tag, attrs,
1092 lambda t: t[1] == 'static method')
1093 attrs = spillproperties("Properties %s:\n" % tag, attrs,
1094 lambda t: t[1] == 'property')
1095 attrs = spilldata("Data and non-method functions %s:\n" % tag,
1096 attrs, lambda t: t[1] == 'data')
1097 assert attrs == []
1098 attrs = inherited
1100 contents = '\n'.join(contents)
1101 if not contents:
1102 return title + '\n'
1103 return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
1105 def formatvalue(self, object):
1106 """Format an argument default value as text."""
1107 return '=' + self.repr(object)
1109 def docroutine(self, object, name=None, mod=None, cl=None):
1110 """Produce text documentation for a function or method object."""
1111 realname = object.__name__
1112 name = name or realname
1113 note = ''
1114 skipdocs = 0
1115 if inspect.ismethod(object):
1116 imclass = object.im_class
1117 if cl:
1118 if imclass is not cl:
1119 note = ' from ' + classname(imclass, mod)
1120 else:
1121 if object.im_self:
1122 note = ' method of %s instance' % classname(
1123 object.im_self.__class__, mod)
1124 else:
1125 note = ' unbound %s method' % classname(imclass,mod)
1126 object = object.im_func
1128 if name == realname:
1129 title = self.bold(realname)
1130 else:
1131 if (cl and cl.__dict__.has_key(realname) and
1132 cl.__dict__[realname] is object):
1133 skipdocs = 1
1134 title = self.bold(name) + ' = ' + realname
1135 if inspect.isfunction(object):
1136 args, varargs, varkw, defaults = inspect.getargspec(object)
1137 argspec = inspect.formatargspec(
1138 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
1139 if realname == '<lambda>':
1140 title = 'lambda'
1141 argspec = argspec[1:-1] # remove parentheses
1142 else:
1143 argspec = '(...)'
1144 decl = title + argspec + note
1146 if skipdocs:
1147 return decl + '\n'
1148 else:
1149 doc = getdoc(object) or ''
1150 return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
1152 def docother(self, object, name=None, mod=None, maxlen=None, doc=None):
1153 """Produce text documentation for a data object."""
1154 repr = self.repr(object)
1155 if maxlen:
1156 line = (name and name + ' = ' or '') + repr
1157 chop = maxlen - len(line)
1158 if chop < 0: repr = repr[:chop] + '...'
1159 line = (name and self.bold(name) + ' = ' or '') + repr
1160 if doc is not None:
1161 line += '\n' + self.indent(str(doc))
1162 return line
1164 # --------------------------------------------------------- user interfaces
1166 def pager(text):
1167 """The first time this is called, determine what kind of pager to use."""
1168 global pager
1169 pager = getpager()
1170 pager(text)
1172 def getpager():
1173 """Decide what method to use for paging through text."""
1174 if type(sys.stdout) is not types.FileType:
1175 return plainpager
1176 if not sys.stdin.isatty() or not sys.stdout.isatty():
1177 return plainpager
1178 if os.environ.get('TERM') in ['dumb', 'emacs']:
1179 return plainpager
1180 if os.environ.has_key('PAGER'):
1181 if sys.platform == 'win32': # pipes completely broken in Windows
1182 return lambda text: tempfilepager(plain(text), os.environ['PAGER'])
1183 elif os.environ.get('TERM') in ['dumb', 'emacs']:
1184 return lambda text: pipepager(plain(text), os.environ['PAGER'])
1185 else:
1186 return lambda text: pipepager(text, os.environ['PAGER'])
1187 if sys.platform == 'win32':
1188 return lambda text: tempfilepager(plain(text), 'more <')
1189 if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0:
1190 return lambda text: pipepager(text, 'less')
1192 import tempfile
1193 filename = tempfile.mktemp()
1194 open(filename, 'w').close()
1195 try:
1196 if hasattr(os, 'system') and os.system('more %s' % filename) == 0:
1197 return lambda text: pipepager(text, 'more')
1198 else:
1199 return ttypager
1200 finally:
1201 os.unlink(filename)
1203 def plain(text):
1204 """Remove boldface formatting from text."""
1205 return re.sub('.\b', '', text)
1207 def pipepager(text, cmd):
1208 """Page through text by feeding it to another program."""
1209 pipe = os.popen(cmd, 'w')
1210 try:
1211 pipe.write(text)
1212 pipe.close()
1213 except IOError:
1214 pass # Ignore broken pipes caused by quitting the pager program.
1216 def tempfilepager(text, cmd):
1217 """Page through text by invoking a program on a temporary file."""
1218 import tempfile
1219 filename = tempfile.mktemp()
1220 file = open(filename, 'w')
1221 file.write(text)
1222 file.close()
1223 try:
1224 os.system(cmd + ' ' + filename)
1225 finally:
1226 os.unlink(filename)
1228 def ttypager(text):
1229 """Page through text on a text terminal."""
1230 lines = split(plain(text), '\n')
1231 try:
1232 import tty
1233 fd = sys.stdin.fileno()
1234 old = tty.tcgetattr(fd)
1235 tty.setcbreak(fd)
1236 getchar = lambda: sys.stdin.read(1)
1237 except (ImportError, AttributeError):
1238 tty = None
1239 getchar = lambda: sys.stdin.readline()[:-1][:1]
1241 try:
1242 r = inc = os.environ.get('LINES', 25) - 1
1243 sys.stdout.write(join(lines[:inc], '\n') + '\n')
1244 while lines[r:]:
1245 sys.stdout.write('-- more --')
1246 sys.stdout.flush()
1247 c = getchar()
1249 if c in ['q', 'Q']:
1250 sys.stdout.write('\r \r')
1251 break
1252 elif c in ['\r', '\n']:
1253 sys.stdout.write('\r \r' + lines[r] + '\n')
1254 r = r + 1
1255 continue
1256 if c in ['b', 'B', '\x1b']:
1257 r = r - inc - inc
1258 if r < 0: r = 0
1259 sys.stdout.write('\n' + join(lines[r:r+inc], '\n') + '\n')
1260 r = r + inc
1262 finally:
1263 if tty:
1264 tty.tcsetattr(fd, tty.TCSAFLUSH, old)
1266 def plainpager(text):
1267 """Simply print unformatted text. This is the ultimate fallback."""
1268 sys.stdout.write(plain(text))
1270 def describe(thing):
1271 """Produce a short description of the given thing."""
1272 if inspect.ismodule(thing):
1273 if thing.__name__ in sys.builtin_module_names:
1274 return 'built-in module ' + thing.__name__
1275 if hasattr(thing, '__path__'):
1276 return 'package ' + thing.__name__
1277 else:
1278 return 'module ' + thing.__name__
1279 if inspect.isbuiltin(thing):
1280 return 'built-in function ' + thing.__name__
1281 if inspect.isclass(thing):
1282 return 'class ' + thing.__name__
1283 if inspect.isfunction(thing):
1284 return 'function ' + thing.__name__
1285 if inspect.ismethod(thing):
1286 return 'method ' + thing.__name__
1287 if type(thing) is types.InstanceType:
1288 return 'instance of ' + thing.__class__.__name__
1289 return type(thing).__name__
1291 def locate(path, forceload=0):
1292 """Locate an object by name or dotted path, importing as necessary."""
1293 parts = split(path, '.')
1294 module, n = None, 0
1295 while n < len(parts):
1296 nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
1297 if nextmodule: module, n = nextmodule, n + 1
1298 else: break
1299 if module:
1300 object = module
1301 for part in parts[n:]:
1302 try: object = getattr(object, part)
1303 except AttributeError: return None
1304 return object
1305 else:
1306 import __builtin__
1307 if hasattr(__builtin__, path):
1308 return getattr(__builtin__, path)
1310 # --------------------------------------- interactive interpreter interface
1312 text = TextDoc()
1313 html = HTMLDoc()
1315 def doc(thing, title='Python Library Documentation: %s', forceload=0):
1316 """Display text documentation, given an object or a path to an object."""
1317 suffix, name = '', None
1318 if type(thing) is type(''):
1319 try:
1320 object = locate(thing, forceload)
1321 except ErrorDuringImport, value:
1322 print value
1323 return
1324 if not object:
1325 print 'no Python documentation found for %s' % repr(thing)
1326 return
1327 parts = split(thing, '.')
1328 if len(parts) > 1: suffix = ' in ' + join(parts[:-1], '.')
1329 name = parts[-1]
1330 thing = object
1332 desc = describe(thing)
1333 module = inspect.getmodule(thing)
1334 if not suffix and module and module is not thing:
1335 suffix = ' in module ' + module.__name__
1336 pager(title % (desc + suffix) + '\n\n' + text.document(thing, name))
1338 def writedoc(key, forceload=0):
1339 """Write HTML documentation to a file in the current directory."""
1340 try:
1341 object = locate(key, forceload)
1342 except ErrorDuringImport, value:
1343 print value
1344 else:
1345 if object:
1346 page = html.page(describe(object),
1347 html.document(object, object.__name__))
1348 file = open(key + '.html', 'w')
1349 file.write(page)
1350 file.close()
1351 print 'wrote', key + '.html'
1352 else:
1353 print 'no Python documentation found for %s' % repr(key)
1355 def writedocs(dir, pkgpath='', done=None):
1356 """Write out HTML documentation for all modules in a directory tree."""
1357 if done is None: done = {}
1358 for file in os.listdir(dir):
1359 path = os.path.join(dir, file)
1360 if ispackage(path):
1361 writedocs(path, pkgpath + file + '.', done)
1362 elif os.path.isfile(path):
1363 modname = inspect.getmodulename(path)
1364 if modname:
1365 modname = pkgpath + modname
1366 if not done.has_key(modname):
1367 done[modname] = 1
1368 writedoc(modname)
1370 class Helper:
1371 keywords = {
1372 'and': 'BOOLEAN',
1373 'assert': ('ref/assert', ''),
1374 'break': ('ref/break', 'while for'),
1375 'class': ('ref/class', 'CLASSES SPECIALMETHODS'),
1376 'continue': ('ref/continue', 'while for'),
1377 'def': ('ref/function', ''),
1378 'del': ('ref/del', 'BASICMETHODS'),
1379 'elif': 'if',
1380 'else': ('ref/if', 'while for'),
1381 'except': 'try',
1382 'exec': ('ref/exec', ''),
1383 'finally': 'try',
1384 'for': ('ref/for', 'break continue while'),
1385 'from': 'import',
1386 'global': ('ref/global', 'NAMESPACES'),
1387 'if': ('ref/if', 'TRUTHVALUE'),
1388 'import': ('ref/import', 'MODULES'),
1389 'in': ('ref/comparisons', 'SEQUENCEMETHODS2'),
1390 'is': 'COMPARISON',
1391 'lambda': ('ref/lambda', 'FUNCTIONS'),
1392 'not': 'BOOLEAN',
1393 'or': 'BOOLEAN',
1394 'pass': 'PASS',
1395 'print': ('ref/print', ''),
1396 'raise': ('ref/raise', 'EXCEPTIONS'),
1397 'return': ('ref/return', 'FUNCTIONS'),
1398 'try': ('ref/try', 'EXCEPTIONS'),
1399 'while': ('ref/while', 'break continue if TRUTHVALUE'),
1402 topics = {
1403 'TYPES': ('ref/types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS FUNCTIONS CLASSES MODULES FILES inspect'),
1404 'STRINGS': ('ref/strings', 'str UNICODE SEQUENCES STRINGMETHODS FORMATTING TYPES'),
1405 'STRINGMETHODS': ('lib/string-methods', 'STRINGS FORMATTING'),
1406 'FORMATTING': ('lib/typesseq-strings', 'OPERATORS'),
1407 'UNICODE': ('ref/unicode', 'encodings unicode TYPES STRING'),
1408 'NUMBERS': ('ref/numbers', 'INTEGER FLOAT COMPLEX TYPES'),
1409 'INTEGER': ('ref/integers', 'int range'),
1410 'FLOAT': ('ref/floating', 'float math'),
1411 'COMPLEX': ('ref/imaginary', 'complex cmath'),
1412 'SEQUENCES': ('lib/typesseq', 'STRINGMETHODS FORMATTING xrange LISTS'),
1413 'MAPPINGS': 'DICTIONARIES',
1414 'FUNCTIONS': ('lib/typesfunctions', 'def TYPES'),
1415 'METHODS': ('lib/typesmethods', 'class def CLASSES TYPES'),
1416 'CODEOBJECTS': ('lib/bltin-code-objects', 'compile FUNCTIONS TYPES'),
1417 'TYPEOBJECTS': ('lib/bltin-type-objects', 'types TYPES'),
1418 'FRAMEOBJECTS': 'TYPES',
1419 'TRACEBACKS': 'TYPES',
1420 'NONE': ('lib/bltin-null-object', ''),
1421 'ELLIPSIS': ('lib/bltin-ellipsis-object', 'SLICINGS'),
1422 'FILES': ('lib/bltin-file-objects', ''),
1423 'SPECIALATTRIBUTES': ('lib/specialattrs', ''),
1424 'CLASSES': ('ref/types', 'class SPECIALMETHODS PRIVATENAMES'),
1425 'MODULES': ('lib/typesmodules', 'import'),
1426 'PACKAGES': 'import',
1427 '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'),
1428 'OPERATORS': 'EXPRESSIONS',
1429 'PRECEDENCE': 'EXPRESSIONS',
1430 'OBJECTS': ('ref/objects', 'TYPES'),
1431 'SPECIALMETHODS': ('ref/specialnames', 'BASICMETHODS ATTRIBUTEMETHODS CALLABLEMETHODS SEQUENCEMETHODS1 MAPPINGMETHODS SEQUENCEMETHODS2 NUMBERMETHODS CLASSES'),
1432 'BASICMETHODS': ('ref/customization', 'cmp hash repr str SPECIALMETHODS'),
1433 'ATTRIBUTEMETHODS': ('ref/attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
1434 'CALLABLEMETHODS': ('ref/callable-types', 'CALLS SPECIALMETHODS'),
1435 'SEQUENCEMETHODS1': ('ref/sequence-types', 'SEQUENCES SEQUENCEMETHODS2 SPECIALMETHODS'),
1436 'SEQUENCEMETHODS2': ('ref/sequence-methods', 'SEQUENCES SEQUENCEMETHODS1 SPECIALMETHODS'),
1437 'MAPPINGMETHODS': ('ref/sequence-types', 'MAPPINGS SPECIALMETHODS'),
1438 'NUMBERMETHODS': ('ref/numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT SPECIALMETHODS'),
1439 'EXECUTION': ('ref/execframes', ''),
1440 'NAMESPACES': ('ref/execframes', 'global ASSIGNMENT DELETION'),
1441 'SCOPING': 'NAMESPACES',
1442 'FRAMES': 'NAMESPACES',
1443 'EXCEPTIONS': ('ref/exceptions', 'try except finally raise'),
1444 'COERCIONS': 'CONVERSIONS',
1445 'CONVERSIONS': ('ref/conversions', ''),
1446 'IDENTIFIERS': ('ref/identifiers', 'keywords SPECIALIDENTIFIERS'),
1447 'SPECIALIDENTIFIERS': ('ref/id-classes', ''),
1448 'PRIVATENAMES': ('ref/atom-identifiers', ''),
1449 'LITERALS': ('ref/atom-literals', 'STRINGS BACKQUOTES NUMBERS TUPLELITERALS LISTLITERALS DICTIONARYLITERALS'),
1450 'TUPLES': 'SEQUENCES',
1451 'TUPLELITERALS': ('ref/exprlists', 'TUPLES LITERALS'),
1452 'LISTS': ('lib/typesseq-mutable', 'LISTLITERALS'),
1453 'LISTLITERALS': ('ref/lists', 'LISTS LITERALS'),
1454 'DICTIONARIES': ('lib/typesmapping', 'DICTIONARYLITERALS'),
1455 'DICTIONARYLITERALS': ('ref/dict', 'DICTIONARIES LITERALS'),
1456 'BACKQUOTES': ('ref/string-conversions', 'repr str STRINGS LITERALS'),
1457 'ATTRIBUTES': ('ref/attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'),
1458 'SUBSCRIPTS': ('ref/subscriptions', 'SEQUENCEMETHODS1'),
1459 'SLICINGS': ('ref/slicings', 'SEQUENCEMETHODS2'),
1460 'CALLS': ('ref/calls', 'EXPRESSIONS'),
1461 'POWER': ('ref/power', 'EXPRESSIONS'),
1462 'UNARY': ('ref/unary', 'EXPRESSIONS'),
1463 'BINARY': ('ref/binary', 'EXPRESSIONS'),
1464 'SHIFTING': ('ref/shifting', 'EXPRESSIONS'),
1465 'BITWISE': ('ref/bitwise', 'EXPRESSIONS'),
1466 'COMPARISON': ('ref/comparisons', 'EXPRESSIONS BASICMETHODS'),
1467 'BOOLEAN': ('ref/lambda', 'EXPRESSIONS TRUTHVALUE'),
1468 'ASSERTION': 'assert',
1469 'ASSIGNMENT': ('ref/assignment', 'AUGMENTEDASSIGNMENT'),
1470 'AUGMENTEDASSIGNMENT': ('ref/augassign', 'NUMBERMETHODS'),
1471 'DELETION': 'del',
1472 'PRINTING': 'print',
1473 'RETURNING': 'return',
1474 'IMPORTING': 'import',
1475 'CONDITIONAL': 'if',
1476 'LOOPING': ('ref/compound', 'for while break continue'),
1477 'TRUTHVALUE': ('lib/truth', 'if while and or not BASICMETHODS'),
1478 'DEBUGGING': ('lib/module-pdb', 'pdb'),
1481 def __init__(self, input, output):
1482 self.input = input
1483 self.output = output
1484 self.docdir = None
1485 execdir = os.path.dirname(sys.executable)
1486 homedir = os.environ.get('PYTHONHOME')
1487 for dir in [os.environ.get('PYTHONDOCS'),
1488 homedir and os.path.join(homedir, 'doc'),
1489 os.path.join(execdir, 'doc'),
1490 '/usr/doc/python-docs-' + split(sys.version)[0],
1491 '/usr/doc/python-' + split(sys.version)[0],
1492 '/usr/doc/python-docs-' + sys.version[:3],
1493 '/usr/doc/python-' + sys.version[:3]]:
1494 if dir and os.path.isdir(os.path.join(dir, 'lib')):
1495 self.docdir = dir
1497 def __repr__(self):
1498 if inspect.stack()[1][3] == '?':
1499 self()
1500 return ''
1501 return '<pydoc.Helper instance>'
1503 def __call__(self, request=None):
1504 if request is not None:
1505 self.help(request)
1506 else:
1507 self.intro()
1508 self.interact()
1509 self.output.write('''
1510 You are now leaving help and returning to the Python interpreter.
1511 If you want to ask for help on a particular object directly from the
1512 interpreter, you can type "help(object)". Executing "help('string')"
1513 has the same effect as typing a particular string at the help> prompt.
1514 ''')
1516 def interact(self):
1517 self.output.write('\n')
1518 while 1:
1519 self.output.write('help> ')
1520 self.output.flush()
1521 try:
1522 request = self.input.readline()
1523 if not request: break
1524 except KeyboardInterrupt: break
1525 request = strip(replace(request, '"', '', "'", ''))
1526 if lower(request) in ['q', 'quit']: break
1527 self.help(request)
1529 def help(self, request):
1530 if type(request) is type(''):
1531 if request == 'help': self.intro()
1532 elif request == 'keywords': self.listkeywords()
1533 elif request == 'topics': self.listtopics()
1534 elif request == 'modules': self.listmodules()
1535 elif request[:8] == 'modules ':
1536 self.listmodules(split(request)[1])
1537 elif self.keywords.has_key(request): self.showtopic(request)
1538 elif self.topics.has_key(request): self.showtopic(request)
1539 elif request: doc(request, 'Help on %s:')
1540 elif isinstance(request, Helper): self()
1541 else: doc(request, 'Help on %s:')
1542 self.output.write('\n')
1544 def intro(self):
1545 self.output.write('''
1546 Welcome to Python %s! This is the online help utility.
1548 If this is your first time using Python, you should definitely check out
1549 the tutorial on the Internet at http://www.python.org/doc/tut/.
1551 Enter the name of any module, keyword, or topic to get help on writing
1552 Python programs and using Python modules. To quit this help utility and
1553 return to the interpreter, just type "quit".
1555 To get a list of available modules, keywords, or topics, type "modules",
1556 "keywords", or "topics". Each module also comes with a one-line summary
1557 of what it does; to list the modules whose summaries contain a given word
1558 such as "spam", type "modules spam".
1559 ''' % sys.version[:3])
1561 def list(self, items, columns=4, width=80):
1562 items = items[:]
1563 items.sort()
1564 colw = width / columns
1565 rows = (len(items) + columns - 1) / columns
1566 for row in range(rows):
1567 for col in range(columns):
1568 i = col * rows + row
1569 if i < len(items):
1570 self.output.write(items[i])
1571 if col < columns - 1:
1572 self.output.write(' ' + ' ' * (colw-1 - len(items[i])))
1573 self.output.write('\n')
1575 def listkeywords(self):
1576 self.output.write('''
1577 Here is a list of the Python keywords. Enter any keyword to get more help.
1579 ''')
1580 self.list(self.keywords.keys())
1582 def listtopics(self):
1583 self.output.write('''
1584 Here is a list of available topics. Enter any topic name to get more help.
1586 ''')
1587 self.list(self.topics.keys())
1589 def showtopic(self, topic):
1590 if not self.docdir:
1591 self.output.write('''
1592 Sorry, topic and keyword documentation is not available because the Python
1593 HTML documentation files could not be found. If you have installed them,
1594 please set the environment variable PYTHONDOCS to indicate their location.
1595 ''')
1596 return
1597 target = self.topics.get(topic, self.keywords.get(topic))
1598 if not target:
1599 self.output.write('no documentation found for %s\n' % repr(topic))
1600 return
1601 if type(target) is type(''):
1602 return self.showtopic(target)
1604 filename, xrefs = target
1605 filename = self.docdir + '/' + filename + '.html'
1606 try:
1607 file = open(filename)
1608 except:
1609 self.output.write('could not read docs from %s\n' % filename)
1610 return
1612 divpat = re.compile('<div[^>]*navigat.*?</div.*?>', re.I | re.S)
1613 addrpat = re.compile('<address.*?>.*?</address.*?>', re.I | re.S)
1614 document = re.sub(addrpat, '', re.sub(divpat, '', file.read()))
1615 file.close()
1617 import htmllib, formatter, StringIO
1618 buffer = StringIO.StringIO()
1619 parser = htmllib.HTMLParser(
1620 formatter.AbstractFormatter(formatter.DumbWriter(buffer)))
1621 parser.start_table = parser.do_p
1622 parser.end_table = lambda parser=parser: parser.do_p({})
1623 parser.start_tr = parser.do_br
1624 parser.start_td = parser.start_th = lambda a, b=buffer: b.write('\t')
1625 parser.feed(document)
1626 buffer = replace(buffer.getvalue(), '\xa0', ' ', '\n', '\n ')
1627 pager(' ' + strip(buffer) + '\n')
1628 if xrefs:
1629 buffer = StringIO.StringIO()
1630 formatter.DumbWriter(buffer).send_flowing_data(
1631 'Related help topics: ' + join(split(xrefs), ', ') + '\n')
1632 self.output.write('\n%s\n' % buffer.getvalue())
1634 def listmodules(self, key=''):
1635 if key:
1636 self.output.write('''
1637 Here is a list of matching modules. Enter any module name to get more help.
1639 ''')
1640 apropos(key)
1641 else:
1642 self.output.write('''
1643 Please wait a moment while I gather a list of all available modules...
1645 ''')
1646 modules = {}
1647 def callback(path, modname, desc, modules=modules):
1648 if modname and modname[-9:] == '.__init__':
1649 modname = modname[:-9] + ' (package)'
1650 if find(modname, '.') < 0:
1651 modules[modname] = 1
1652 ModuleScanner().run(callback)
1653 self.list(modules.keys())
1654 self.output.write('''
1655 Enter any module name to get more help. Or, type "modules spam" to search
1656 for modules whose descriptions contain the word "spam".
1657 ''')
1659 help = Helper(sys.stdin, sys.stdout)
1661 class Scanner:
1662 """A generic tree iterator."""
1663 def __init__(self, roots, children, descendp):
1664 self.roots = roots[:]
1665 self.state = []
1666 self.children = children
1667 self.descendp = descendp
1669 def next(self):
1670 if not self.state:
1671 if not self.roots:
1672 return None
1673 root = self.roots.pop(0)
1674 self.state = [(root, self.children(root))]
1675 node, children = self.state[-1]
1676 if not children:
1677 self.state.pop()
1678 return self.next()
1679 child = children.pop(0)
1680 if self.descendp(child):
1681 self.state.append((child, self.children(child)))
1682 return child
1684 class ModuleScanner(Scanner):
1685 """An interruptible scanner that searches module synopses."""
1686 def __init__(self):
1687 roots = map(lambda dir: (dir, ''), pathdirs())
1688 Scanner.__init__(self, roots, self.submodules, self.isnewpackage)
1689 self.inodes = map(lambda (dir, pkg): os.stat(dir)[1], roots)
1691 def submodules(self, (dir, package)):
1692 children = []
1693 for file in os.listdir(dir):
1694 path = os.path.join(dir, file)
1695 if ispackage(path):
1696 children.append((path, package + (package and '.') + file))
1697 else:
1698 children.append((path, package))
1699 children.sort() # so that spam.py comes before spam.pyc or spam.pyo
1700 return children
1702 def isnewpackage(self, (dir, package)):
1703 inode = os.path.exists(dir) and os.stat(dir)[1]
1704 if not (os.path.islink(dir) and inode in self.inodes):
1705 self.inodes.append(inode) # detect circular symbolic links
1706 return ispackage(dir)
1708 def run(self, callback, key=None, completer=None):
1709 if key: key = lower(key)
1710 self.quit = 0
1711 seen = {}
1713 for modname in sys.builtin_module_names:
1714 if modname != '__main__':
1715 seen[modname] = 1
1716 if key is None:
1717 callback(None, modname, '')
1718 else:
1719 desc = split(__import__(modname).__doc__ or '', '\n')[0]
1720 if find(lower(modname + ' - ' + desc), key) >= 0:
1721 callback(None, modname, desc)
1723 while not self.quit:
1724 node = self.next()
1725 if not node: break
1726 path, package = node
1727 modname = inspect.getmodulename(path)
1728 if os.path.isfile(path) and modname:
1729 modname = package + (package and '.') + modname
1730 if not seen.has_key(modname):
1731 seen[modname] = 1 # if we see spam.py, skip spam.pyc
1732 if key is None:
1733 callback(path, modname, '')
1734 else:
1735 desc = synopsis(path) or ''
1736 if find(lower(modname + ' - ' + desc), key) >= 0:
1737 callback(path, modname, desc)
1738 if completer: completer()
1740 def apropos(key):
1741 """Print all the one-line module summaries that contain a substring."""
1742 def callback(path, modname, desc):
1743 if modname[-9:] == '.__init__':
1744 modname = modname[:-9] + ' (package)'
1745 print modname, desc and '- ' + desc
1746 try: import warnings
1747 except ImportError: pass
1748 else: warnings.filterwarnings('ignore') # ignore problems during import
1749 ModuleScanner().run(callback, key)
1751 # --------------------------------------------------- web browser interface
1753 def serve(port, callback=None, completer=None):
1754 import BaseHTTPServer, mimetools, select
1756 # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
1757 class Message(mimetools.Message):
1758 def __init__(self, fp, seekable=1):
1759 Message = self.__class__
1760 Message.__bases__[0].__bases__[0].__init__(self, fp, seekable)
1761 self.encodingheader = self.getheader('content-transfer-encoding')
1762 self.typeheader = self.getheader('content-type')
1763 self.parsetype()
1764 self.parseplist()
1766 class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1767 def send_document(self, title, contents):
1768 try:
1769 self.send_response(200)
1770 self.send_header('Content-Type', 'text/html')
1771 self.end_headers()
1772 self.wfile.write(html.page(title, contents))
1773 except IOError: pass
1775 def do_GET(self):
1776 path = self.path
1777 if path[-5:] == '.html': path = path[:-5]
1778 if path[:1] == '/': path = path[1:]
1779 if path and path != '.':
1780 try:
1781 obj = locate(path, forceload=1)
1782 except ErrorDuringImport, value:
1783 self.send_document(path, html.escape(str(value)))
1784 return
1785 if obj:
1786 self.send_document(describe(obj), html.document(obj, path))
1787 else:
1788 self.send_document(path,
1789 'no Python documentation found for %s' % repr(path))
1790 else:
1791 heading = html.heading(
1792 '<big><big><strong>Python: Index of Modules</strong></big></big>',
1793 '#ffffff', '#7799ee')
1794 def bltinlink(name):
1795 return '<a href="%s.html">%s</a>' % (name, name)
1796 names = filter(lambda x: x != '__main__',
1797 sys.builtin_module_names)
1798 contents = html.multicolumn(names, bltinlink)
1799 indices = ['<p>' + html.bigsection(
1800 'Built-in Modules', '#ffffff', '#ee77aa', contents)]
1802 seen = {}
1803 for dir in pathdirs():
1804 indices.append(html.index(dir, seen))
1805 contents = heading + join(indices) + '''<p align=right>
1806 <font color="#909090" face="helvetica, arial"><strong>
1807 pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font>'''
1808 self.send_document('Index of Modules', contents)
1810 def log_message(self, *args): pass
1812 class DocServer(BaseHTTPServer.HTTPServer):
1813 def __init__(self, port, callback):
1814 host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
1815 self.address = ('', port)
1816 self.url = 'http://%s:%d/' % (host, port)
1817 self.callback = callback
1818 self.base.__init__(self, self.address, self.handler)
1820 def serve_until_quit(self):
1821 import select
1822 self.quit = 0
1823 while not self.quit:
1824 rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
1825 if rd: self.handle_request()
1827 def server_activate(self):
1828 self.base.server_activate(self)
1829 if self.callback: self.callback(self)
1831 DocServer.base = BaseHTTPServer.HTTPServer
1832 DocServer.handler = DocHandler
1833 DocHandler.MessageClass = Message
1834 try:
1835 try:
1836 DocServer(port, callback).serve_until_quit()
1837 except (KeyboardInterrupt, select.error):
1838 pass
1839 finally:
1840 if completer: completer()
1842 # ----------------------------------------------------- graphical interface
1844 def gui():
1845 """Graphical interface (starts web server and pops up a control window)."""
1846 class GUI:
1847 def __init__(self, window, port=7464):
1848 self.window = window
1849 self.server = None
1850 self.scanner = None
1852 import Tkinter
1853 self.server_frm = Tkinter.Frame(window)
1854 self.title_lbl = Tkinter.Label(self.server_frm,
1855 text='Starting server...\n ')
1856 self.open_btn = Tkinter.Button(self.server_frm,
1857 text='open browser', command=self.open, state='disabled')
1858 self.quit_btn = Tkinter.Button(self.server_frm,
1859 text='quit serving', command=self.quit, state='disabled')
1861 self.search_frm = Tkinter.Frame(window)
1862 self.search_lbl = Tkinter.Label(self.search_frm, text='Search for')
1863 self.search_ent = Tkinter.Entry(self.search_frm)
1864 self.search_ent.bind('<Return>', self.search)
1865 self.stop_btn = Tkinter.Button(self.search_frm,
1866 text='stop', pady=0, command=self.stop, state='disabled')
1867 if sys.platform == 'win32':
1868 # Trying to hide and show this button crashes under Windows.
1869 self.stop_btn.pack(side='right')
1871 self.window.title('pydoc')
1872 self.window.protocol('WM_DELETE_WINDOW', self.quit)
1873 self.title_lbl.pack(side='top', fill='x')
1874 self.open_btn.pack(side='left', fill='x', expand=1)
1875 self.quit_btn.pack(side='right', fill='x', expand=1)
1876 self.server_frm.pack(side='top', fill='x')
1878 self.search_lbl.pack(side='left')
1879 self.search_ent.pack(side='right', fill='x', expand=1)
1880 self.search_frm.pack(side='top', fill='x')
1881 self.search_ent.focus_set()
1883 font = ('helvetica', sys.platform == 'win32' and 8 or 10)
1884 self.result_lst = Tkinter.Listbox(window, font=font, height=6)
1885 self.result_lst.bind('<Button-1>', self.select)
1886 self.result_lst.bind('<Double-Button-1>', self.goto)
1887 self.result_scr = Tkinter.Scrollbar(window,
1888 orient='vertical', command=self.result_lst.yview)
1889 self.result_lst.config(yscrollcommand=self.result_scr.set)
1891 self.result_frm = Tkinter.Frame(window)
1892 self.goto_btn = Tkinter.Button(self.result_frm,
1893 text='go to selected', command=self.goto)
1894 self.hide_btn = Tkinter.Button(self.result_frm,
1895 text='hide results', command=self.hide)
1896 self.goto_btn.pack(side='left', fill='x', expand=1)
1897 self.hide_btn.pack(side='right', fill='x', expand=1)
1899 self.window.update()
1900 self.minwidth = self.window.winfo_width()
1901 self.minheight = self.window.winfo_height()
1902 self.bigminheight = (self.server_frm.winfo_reqheight() +
1903 self.search_frm.winfo_reqheight() +
1904 self.result_lst.winfo_reqheight() +
1905 self.result_frm.winfo_reqheight())
1906 self.bigwidth, self.bigheight = self.minwidth, self.bigminheight
1907 self.expanded = 0
1908 self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
1909 self.window.wm_minsize(self.minwidth, self.minheight)
1911 import threading
1912 threading.Thread(
1913 target=serve, args=(port, self.ready, self.quit)).start()
1915 def ready(self, server):
1916 self.server = server
1917 self.title_lbl.config(
1918 text='Python documentation server at\n' + server.url)
1919 self.open_btn.config(state='normal')
1920 self.quit_btn.config(state='normal')
1922 def open(self, event=None, url=None):
1923 url = url or self.server.url
1924 try:
1925 import webbrowser
1926 webbrowser.open(url)
1927 except ImportError: # pre-webbrowser.py compatibility
1928 if sys.platform == 'win32':
1929 os.system('start "%s"' % url)
1930 elif sys.platform == 'mac':
1931 try: import ic
1932 except ImportError: pass
1933 else: ic.launchurl(url)
1934 else:
1935 rc = os.system('netscape -remote "openURL(%s)" &' % url)
1936 if rc: os.system('netscape "%s" &' % url)
1938 def quit(self, event=None):
1939 if self.server:
1940 self.server.quit = 1
1941 self.window.quit()
1943 def search(self, event=None):
1944 key = self.search_ent.get()
1945 self.stop_btn.pack(side='right')
1946 self.stop_btn.config(state='normal')
1947 self.search_lbl.config(text='Searching for "%s"...' % key)
1948 self.search_ent.forget()
1949 self.search_lbl.pack(side='left')
1950 self.result_lst.delete(0, 'end')
1951 self.goto_btn.config(state='disabled')
1952 self.expand()
1954 import threading
1955 if self.scanner:
1956 self.scanner.quit = 1
1957 self.scanner = ModuleScanner()
1958 threading.Thread(target=self.scanner.run,
1959 args=(self.update, key, self.done)).start()
1961 def update(self, path, modname, desc):
1962 if modname[-9:] == '.__init__':
1963 modname = modname[:-9] + ' (package)'
1964 self.result_lst.insert('end',
1965 modname + ' - ' + (desc or '(no description)'))
1967 def stop(self, event=None):
1968 if self.scanner:
1969 self.scanner.quit = 1
1970 self.scanner = None
1972 def done(self):
1973 self.scanner = None
1974 self.search_lbl.config(text='Search for')
1975 self.search_lbl.pack(side='left')
1976 self.search_ent.pack(side='right', fill='x', expand=1)
1977 if sys.platform != 'win32': self.stop_btn.forget()
1978 self.stop_btn.config(state='disabled')
1980 def select(self, event=None):
1981 self.goto_btn.config(state='normal')
1983 def goto(self, event=None):
1984 selection = self.result_lst.curselection()
1985 if selection:
1986 modname = split(self.result_lst.get(selection[0]))[0]
1987 self.open(url=self.server.url + modname + '.html')
1989 def collapse(self):
1990 if not self.expanded: return
1991 self.result_frm.forget()
1992 self.result_scr.forget()
1993 self.result_lst.forget()
1994 self.bigwidth = self.window.winfo_width()
1995 self.bigheight = self.window.winfo_height()
1996 self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
1997 self.window.wm_minsize(self.minwidth, self.minheight)
1998 self.expanded = 0
2000 def expand(self):
2001 if self.expanded: return
2002 self.result_frm.pack(side='bottom', fill='x')
2003 self.result_scr.pack(side='right', fill='y')
2004 self.result_lst.pack(side='top', fill='both', expand=1)
2005 self.window.wm_geometry('%dx%d' % (self.bigwidth, self.bigheight))
2006 self.window.wm_minsize(self.minwidth, self.bigminheight)
2007 self.expanded = 1
2009 def hide(self, event=None):
2010 self.stop()
2011 self.collapse()
2013 import Tkinter
2014 try:
2015 gui = GUI(Tkinter.Tk())
2016 Tkinter.mainloop()
2017 except KeyboardInterrupt:
2018 pass
2020 # -------------------------------------------------- command-line interface
2022 def ispath(x):
2023 return type(x) is types.StringType and find(x, os.sep) >= 0
2025 def cli():
2026 """Command-line interface (looks at sys.argv to decide what to do)."""
2027 import getopt
2028 class BadUsage: pass
2030 # Scripts don't get the current directory in their path by default.
2031 scriptdir = os.path.dirname(sys.argv[0])
2032 if scriptdir in sys.path:
2033 sys.path.remove(scriptdir)
2034 sys.path.insert(0, '.')
2036 try:
2037 opts, args = getopt.getopt(sys.argv[1:], 'gk:p:w')
2038 writing = 0
2040 for opt, val in opts:
2041 if opt == '-g':
2042 gui()
2043 return
2044 if opt == '-k':
2045 apropos(val)
2046 return
2047 if opt == '-p':
2048 try:
2049 port = int(val)
2050 except ValueError:
2051 raise BadUsage
2052 def ready(server):
2053 print 'pydoc server ready at %s' % server.url
2054 def stopped():
2055 print 'pydoc server stopped'
2056 serve(port, ready, stopped)
2057 return
2058 if opt == '-w':
2059 writing = 1
2061 if not args: raise BadUsage
2062 for arg in args:
2063 try:
2064 if ispath(arg) and os.path.isfile(arg):
2065 arg = importfile(arg)
2066 if writing:
2067 if ispath(arg) and os.path.isdir(arg):
2068 writedocs(arg)
2069 else:
2070 writedoc(arg)
2071 else:
2072 doc(arg)
2073 except ErrorDuringImport, value:
2074 print value
2076 except (getopt.error, BadUsage):
2077 cmd = sys.argv[0]
2078 print """pydoc - the Python documentation tool
2080 %s <name> ...
2081 Show text documentation on something. <name> may be the name of a
2082 function, module, or package, or a dotted reference to a class or
2083 function within a module or module in a package. If <name> contains
2084 a '%s', it is used as the path to a Python source file to document.
2086 %s -k <keyword>
2087 Search for a keyword in the synopsis lines of all available modules.
2089 %s -p <port>
2090 Start an HTTP server on the given port on the local machine.
2092 %s -g
2093 Pop up a graphical interface for finding and serving documentation.
2095 %s -w <name> ...
2096 Write out the HTML documentation for a module to a file in the current
2097 directory. If <name> contains a '%s', it is treated as a filename; if
2098 it names a directory, documentation is written for all the contents.
2099 """ % (cmd, os.sep, cmd, cmd, cmd, cmd, os.sep)
2101 if __name__ == '__main__': cli()