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