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