#!/usr/bin/env python """Generate Python documentation in HTML or as text for interactive use. At the shell command line outside of Python, run "pydoc " to show documentation on something. may be the name of a Python function, module, package, or a dotted reference to a class or function within a module or module in a package. Alternatively, the argument can be the path to a Python source file. Or, at the shell prompt, run "pydoc -k " to search for a keyword in the one-line descriptions of modules. Or, at the shell prompt, run "pydoc -p " to start an HTTP server on a given port on the local machine to generate documentation web pages. Or, at the shell prompt, run "pydoc -w " to write out the HTML documentation for a module to a file named ".html". In the Python interpreter, do "from pydoc import help" to provide online help. Calling help(thing) on a Python object documents the object.""" __author__ = "Ka-Ping Yee " __version__ = "26 February 2001" import sys, imp, os, stat, re, types, inspect from repr import Repr from string import expandtabs, find, join, lower, split, strip, rstrip # --------------------------------------------------------- common routines def synopsis(filename, cache={}): """Get the one-line summary out of a module file.""" mtime = os.stat(filename)[stat.ST_MTIME] lastupdate, result = cache.get(filename, (0, None)) if lastupdate < mtime: file = open(filename) line = file.readline() while line[:1] == '#' or strip(line) == '': line = file.readline() if not line: break if line[-2:] == '\\\n': line = line[:-2] + file.readline() line = strip(line) if line[:3] == '"""': line = line[3:] while strip(line) == '': line = file.readline() if not line: break result = split(line, '"""')[0] else: result = None file.close() cache[filename] = (mtime, result) return result def index(dir): """Return a list of (module-name, synopsis) pairs for a directory tree.""" results = [] for entry in os.listdir(dir): path = os.path.join(dir, entry) if ispackage(path): results.extend(map( lambda (m, s), pkg=entry: (pkg + '.' + m, s), index(path))) elif os.path.isfile(path) and entry[-3:] == '.py': results.append((entry[:-3], synopsis(path))) return results def pathdirs(): """Convert sys.path into a list of absolute, existing, unique paths.""" dirs = [] for dir in sys.path: dir = os.path.abspath(dir or '.') if dir not in dirs and os.path.isdir(dir): dirs.append(dir) return dirs def getdoc(object): """Get the doc string or comments for an object.""" result = inspect.getdoc(object) if not result: try: result = inspect.getcomments(object) except: pass return result and rstrip(result) or '' def classname(object, modname): """Get a class name and qualify it with a module name if necessary.""" name = object.__name__ if object.__module__ != modname: name = object.__module__ + '.' + name return name def isconstant(object): """Check if an object is of a type that probably means it's a constant.""" return type(object) in [ types.FloatType, types.IntType, types.ListType, types.LongType, types.StringType, types.TupleType, types.TypeType, hasattr(types, 'UnicodeType') and types.UnicodeType or 0] def replace(text, *pairs): """Do a series of global replacements on a string.""" for old, new in pairs: text = join(split(text, old), new) return text def cram(text, maxlen): """Omit part of a string if needed to make it fit in a maximum length.""" if len(text) > maxlen: pre = max(0, (maxlen-3)/2) post = max(0, maxlen-3-pre) return text[:pre] + '...' + text[len(text)-post:] return text def cleanid(text): """Remove the hexadecimal id from a Python object representation.""" return re.sub(' at 0x[0-9a-f]{5,}>$', '>', text) def modulename(path): """Return the Python module name for a given path, or None.""" filename = os.path.basename(path) if lower(filename[-3:]) == '.py': return filename[:-3] elif lower(filename[-4:]) == '.pyc': return filename[:-4] elif lower(filename[-11:]) == 'module.so': return filename[:-11] elif lower(filename[-13:]) == 'module.so.1': return filename[:-13] class DocImportError(Exception): """Class for errors while trying to import something to document it.""" def __init__(self, filename, etype, evalue): self.filename = filename self.etype = etype self.evalue = evalue if type(etype) is types.ClassType: etype = etype.__name__ self.args = '%s: %s' % (etype, evalue) def importfile(path): """Import a Python source file or compiled file given its path.""" magic = imp.get_magic() file = open(path, 'r') if file.read(len(magic)) == magic: kind = imp.PY_COMPILED else: kind = imp.PY_SOURCE file.close() filename = os.path.basename(path) name, ext = os.path.splitext(filename) file = open(path, 'r') try: module = imp.load_module(name, file, path, (ext, 'r', kind)) except: raise DocImportError(path, sys.exc_type, sys.exc_value) file.close() return module def ispackage(path): """Guess whether a path refers to a package directory.""" if os.path.isdir(path): init = os.path.join(path, '__init__.py') initc = os.path.join(path, '__init__.pyc') if os.path.isfile(init) or os.path.isfile(initc): return 1 # ---------------------------------------------------- formatter base class class Doc: def document(self, object, *args): """Generate documentation for an object.""" args = (object,) + args if inspect.ismodule(object): return apply(self.docmodule, args) if inspect.isclass(object): return apply(self.docclass, args) if inspect.ismethod(object): return apply(self.docmethod, args) if inspect.isbuiltin(object): return apply(self.docbuiltin, args) if inspect.isfunction(object): return apply(self.docfunction, args) raise TypeError, "don't know how to document objects of type " + \ type(object).__name__ # -------------------------------------------- HTML documentation generator class HTMLRepr(Repr): """Class for safely making an HTML representation of a Python object.""" def __init__(self): Repr.__init__(self) self.maxlist = self.maxtuple = self.maxdict = 10 self.maxstring = self.maxother = 50 def escape(self, text): return replace(text, ('&', '&'), ('<', '<'), ('>', '>')) def repr(self, object): result = Repr.repr(self, object) return result def repr1(self, x, level): methodname = 'repr_' + join(split(type(x).__name__), '_') if hasattr(self, methodname): return getattr(self, methodname)(x, level) else: return self.escape(cram(cleanid(repr(x)), self.maxother)) def repr_string(self, x, level): text = self.escape(cram(x, self.maxstring)) return re.sub(r'((\\[\\abfnrtv]|\\x..|\\u....)+)', r'\1', repr(text)) def repr_instance(self, x, level): try: return cram(cleanid(repr(x)), self.maxstring) except: return self.escape('<%s instance>' % x.__class__.__name__) repr_unicode = repr_string class HTMLDoc(Doc): """Formatter class for HTML documentation.""" # ------------------------------------------- HTML formatting utilities _repr_instance = HTMLRepr() repr = _repr_instance.repr escape = _repr_instance.escape def preformat(self, text): """Format literal preformatted text.""" text = self.escape(expandtabs(text)) return replace(text, ('\n\n', '\n \n'), ('\n\n', '\n \n'), (' ', ' '), ('\n', '
\n')) def multicolumn(self, list, format, cols=4): """Format a list of items into a multi-column list.""" result = '' rows = (len(list)+cols-1)/cols for col in range(cols): result = result + '' % (100/cols) for i in range(rows*col, rows*col+rows): if i < len(list): result = result + format(list[i]) + '
' result = result + '' return '%s
' % result def heading(self, title, fgcol, bgcol, extras=''): """Format a page heading.""" return """


 %s
 %s
""" % (bgcol, fgcol, title, fgcol, extras) def section(self, title, fgcol, bgcol, contents, width=20, prelude='', marginalia=None, gap='   '): """Format a section with a heading.""" if marginalia is None: marginalia = ' ' * width result = """

""" % (bgcol, fgcol, title) if prelude: result = result + """ """ % (bgcol, marginalia, bgcol, prelude) result = result + """ """ % (bgcol, marginalia, gap) result = result + '

 %s
%s %s
%s%s%s
' % contents return result def bigsection(self, title, *args): """Format a section with a big heading.""" title = '%s' % title return apply(self.section, (title,) + args) def footer(self): return """
generated with htmldoc by Ka-Ping Yee
""" def namelink(self, name, *dicts): """Make a link for an identifier, given name-to-URL mappings.""" for dict in dicts: if dict.has_key(name): return '%s' % (dict[name], name) return name def classlink(self, object, modname, *dicts): """Make a link for a class.""" name = object.__name__ if object.__module__ != modname: name = object.__module__ + '.' + name for dict in dicts: if dict.has_key(object): return '%s' % (dict[object], name) return name def modulelink(self, object): """Make a link for a module.""" return '%s' % (object.__name__, object.__name__) def modpkglink(self, (name, path, ispackage, shadowed)): """Make a link for a module or package to display in an index.""" if shadowed: return '%s' % name if path: url = '%s.%s.html' % (path, name) else: url = '%s.html' % name if ispackage: text = '%s (package)' % name else: text = name return '%s' % (url, text) def markup(self, text, escape=None, funcs={}, classes={}, methods={}): """Mark up some plain text, given a context of symbols to look for. Each context dictionary maps object names to anchor names.""" escape = escape or self.escape results = [] here = 0 pattern = re.compile(r'\b(((http|ftp)://\S+[\w/])|' r'(RFC[- ]?(\d+))|' r'(self\.)?(\w+))\b') while 1: match = pattern.search(text, here) if not match: break start, end = match.span() results.append(escape(text[here:start])) all, url, scheme, rfc, rfcnum, selfdot, name = match.groups() if url: results.append('%s' % (url, escape(url))) elif rfc: url = 'http://www.rfc-editor.org/rfc/rfc%s.txt' % rfcnum results.append('%s' % (url, escape(rfc))) else: if text[end:end+1] == '(': results.append(self.namelink(name, methods, funcs, classes)) elif selfdot: results.append('self.%s' % name) else: results.append(self.namelink(name, classes)) here = end results.append(escape(text[here:])) return join(results, '') # ---------------------------------------------- type-specific routines def doctree(self, tree, modname, classes={}, parent=None): """Produce HTML for a class tree as given by inspect.getclasstree().""" result = '' for entry in tree: if type(entry) is type(()): c, bases = entry result = result + '

' result = result + self.classlink(c, modname, classes) if bases and bases != (parent,): parents = [] for base in bases: parents.append(self.classlink(base, modname, classes)) result = result + '(' + join(parents, ', ') + ')' result = result + '\n
' elif type(entry) is type([]): result = result + \ '
\n%s
\n' % self.doctree(entry, modname, classes, c) return '
\n%s
\n' % result def docmodule(self, object): """Produce HTML documentation for a module object.""" name = object.__name__ result = '' head = '
 %s' % name try: file = inspect.getsourcefile(object) filelink = '%s' % (file, file) except TypeError: filelink = '(built-in)' if hasattr(object, '__version__'): head = head + ' (version: %s)' % self.escape(object.__version__) result = result + self.heading( head, '#ffffff', '#7799ee', 'index
' + filelink) second = lambda list: list[1] modules = map(second, inspect.getmembers(object, inspect.ismodule)) classes, cdict = [], {} for key, value in inspect.getmembers(object, inspect.isclass): if (inspect.getmodule(value) or object) is object: classes.append(value) cdict[key] = cdict[value] = '#' + key funcs, fdict = [], {} for key, value in inspect.getmembers(object, inspect.isroutine): if inspect.isbuiltin(value) or inspect.getmodule(value) is object: funcs.append(value) fdict[key] = '#-' + key if inspect.isfunction(value): fdict[value] = fdict[key] for c in classes: for base in c.__bases__: key, modname = base.__name__, base.__module__ if modname != name and sys.modules.has_key(modname): module = sys.modules[modname] if hasattr(module, key) and getattr(module, key) is base: if not cdict.has_key(key): cdict[key] = cdict[base] = modname + '.html#' + key constants = [] for key, value in inspect.getmembers(object, isconstant): if key[:1] != '_': constants.append((key, value)) doc = self.markup(getdoc(object), self.preformat, fdict, cdict) doc = doc and '%s' % doc result = result + '

%s

\n' % doc if hasattr(object, '__path__'): modpkgs = [] modnames = [] for file in os.listdir(object.__path__[0]): if file[:1] != '_': path = os.path.join(object.__path__[0], file) modname = modulename(file) if modname and modname not in modnames: modpkgs.append((modname, name, 0, 0)) modnames.append(modname) elif ispackage(path): modpkgs.append((file, name, 1, 0)) modpkgs.sort() contents = self.multicolumn(modpkgs, self.modpkglink) result = result + self.bigsection( 'Package Contents', '#ffffff', '#aa55cc', contents) elif modules: contents = self.multicolumn(modules, self.modulelink) result = result + self.bigsection( 'Modules', '#fffff', '#aa55cc', contents) if classes: contents = self.doctree( inspect.getclasstree(classes, 1), name, cdict) for item in classes: contents = contents + self.document(item, fdict, cdict) result = result + self.bigsection( 'Classes', '#ffffff', '#ee77aa', contents) if funcs: contents = '' for item in funcs: contents = contents + self.document(item, fdict, cdict) result = result + self.bigsection( 'Functions', '#ffffff', '#eeaa77', contents) if constants: contents = '' for key, value in constants: contents = contents + ('
%s = %s' % (key, self.repr(value))) result = result + self.bigsection( 'Constants', '#ffffff', '#55aa55', contents) return result def docclass(self, object, funcs={}, classes={}): """Produce HTML documentation for a class object.""" name = object.__name__ bases = object.__bases__ contents = '' methods, mdict = [], {} for key, value in inspect.getmembers(object, inspect.ismethod): methods.append(value) mdict[key] = mdict[value] = '#' + name + '-' + key for item in methods: contents = contents + self.document( item, funcs, classes, mdict, name) title = 'class %s' % (name, name) if bases: parents = [] for base in bases: parents.append(self.classlink(base, object.__module__, classes)) title = title + '(%s)' % join(parents, ', ') doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict) if doc: doc = '' + doc + '
 
' return self.section(title, '#000000', '#ffc8d8', contents, 10, doc) def docmethod(self, object, funcs={}, classes={}, methods={}, clname=''): """Produce HTML documentation for a method object.""" return self.document( object.im_func, funcs, classes, methods, clname) def formatvalue(self, object): """Format an argument default value as text.""" return ('=%s' % self.repr(object)) def docfunction(self, object, funcs={}, classes={}, methods={}, clname=''): """Produce HTML documentation for a function object.""" args, varargs, varkw, defaults = inspect.getargspec(object) argspec = inspect.formatargspec( args, varargs, varkw, defaults, formatvalue=self.formatvalue) if object.__name__ == '': decl = 'lambda ' + argspec[1:-1] else: anchor = clname + '-' + object.__name__ decl = '%s%s\n' % ( anchor, object.__name__, argspec) doc = self.markup(getdoc(object), self.preformat, funcs, classes, methods) doc = replace(doc, ('
\n', '
')) doc = doc and '%s' % doc return '
%s
%s
' % (decl, doc) def docbuiltin(self, object, *extras): """Produce HTML documentation for a built-in function.""" return '
%s(...)
' % object.__name__ def page(self, object): """Produce a complete HTML page of documentation for an object.""" return ''' Python: %s %s ''' % (describe(object), self.document(object)) def index(self, dir, shadowed=None): """Generate an HTML index for a directory of modules.""" modpkgs = [] if shadowed is None: shadowed = {} seen = {} files = os.listdir(dir) def found(name, ispackage, modpkgs=modpkgs, shadowed=shadowed, seen=seen): if not seen.has_key(name): modpkgs.append((name, '', ispackage, shadowed.has_key(name))) seen[name] = 1 shadowed[name] = 1 # Package spam/__init__.py takes precedence over module spam.py. for file in files: path = os.path.join(dir, file) if ispackage(path): found(file, 1) for file in files: path = os.path.join(dir, file) if file[:1] != '_' and os.path.isfile(path): modname = modulename(file) if modname: found(modname, 0) modpkgs.sort() contents = self.multicolumn(modpkgs, self.modpkglink) return self.bigsection(dir, '#ffffff', '#ee77aa', contents) # -------------------------------------------- text documentation generator class TextRepr(Repr): """Class for safely making a text representation of a Python object.""" def __init__(self): Repr.__init__(self) self.maxlist = self.maxtuple = self.maxdict = 10 self.maxstring = self.maxother = 50 def repr1(self, x, level): methodname = 'repr_' + join(split(type(x).__name__), '_') if hasattr(self, methodname): return getattr(self, methodname)(x, level) else: return cram(cleanid(repr(x)), self.maxother) def repr_instance(self, x, level): try: return cram(cleanid(repr(x)), self.maxstring) except: return '<%s instance>' % x.__class__.__name__ class TextDoc(Doc): """Formatter class for text documentation.""" # ------------------------------------------- text formatting utilities _repr_instance = TextRepr() repr = _repr_instance.repr def bold(self, text): """Format a string in bold by overstriking.""" return join(map(lambda ch: ch + '\b' + ch, text), '') def indent(self, text, prefix=' '): """Indent text by prepending a given prefix to each line.""" if not text: return '' lines = split(text, '\n') lines = map(lambda line, prefix=prefix: prefix + line, lines) if lines: lines[-1] = rstrip(lines[-1]) return join(lines, '\n') def section(self, title, contents): """Format a section with a given heading.""" return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n' # ---------------------------------------------- type-specific routines def doctree(self, tree, modname, parent=None, prefix=''): """Render in text a class tree as returned by inspect.getclasstree().""" result = '' for entry in tree: if type(entry) is type(()): cl, bases = entry result = result + prefix + classname(cl, modname) if bases and bases != (parent,): parents = map(lambda cl, m=modname: classname(cl, m), bases) result = result + '(%s)' % join(parents, ', ') result = result + '\n' elif type(entry) is type([]): result = result + self.doctree( entry, modname, cl, prefix + ' ') return result def docmodule(self, object): """Produce text documentation for a given module object.""" result = '' name = object.__name__ lines = split(strip(getdoc(object)), '\n') if len(lines) == 1: if lines[0]: name = name + ' - ' + lines[0] lines = [] elif len(lines) >= 2 and not rstrip(lines[1]): if lines[0]: name = name + ' - ' + lines[0] lines = lines[2:] result = result + self.section('NAME', name) try: file = inspect.getfile(object) # XXX or getsourcefile? except TypeError: file = None result = result + self.section('FILE', file or '(built-in)') if lines: result = result + self.section('DESCRIPTION', join(lines, '\n')) classes = [] for key, value in inspect.getmembers(object, inspect.isclass): if (inspect.getmodule(value) or object) is object: classes.append(value) funcs = [] for key, value in inspect.getmembers(object, inspect.isroutine): if inspect.isbuiltin(value) or inspect.getmodule(value) is object: funcs.append(value) constants = [] for key, value in inspect.getmembers(object, isconstant): if key[:1] != '_': constants.append((key, value)) if hasattr(object, '__path__'): modpkgs = [] for file in os.listdir(object.__path__[0]): if file[:1] != '_': path = os.path.join(object.__path__[0], file) modname = modulename(file) if modname and modname not in modpkgs: modpkgs.append(modname) elif ispackage(path): modpkgs.append(file + ' (package)') modpkgs.sort() result = result + self.section( 'PACKAGE CONTENTS', join(modpkgs, '\n')) if classes: contents = self.doctree( inspect.getclasstree(classes, 1), object.__name__) + '\n' for item in classes: contents = contents + self.document(item) + '\n' result = result + self.section('CLASSES', contents) if funcs: contents = '' for item in funcs: contents = contents + self.document(item) + '\n' result = result + self.section('FUNCTIONS', contents) if constants: contents = '' for key, value in constants: line = key + ' = ' + self.repr(value) chop = 70 - len(line) line = self.bold(key) + ' = ' + self.repr(value) if chop < 0: line = line[:chop] + '...' contents = contents + line + '\n' result = result + self.section('CONSTANTS', contents) if hasattr(object, '__version__'): version = str(object.__version__) if hasattr(object, '__date__'): version = version + ', ' + str(object.__date__) result = result + self.section('VERSION', version) if hasattr(object, '__author__'): author = str(object.__author__) if hasattr(object, '__email__'): author = author + ' <' + str(object.__email__) + '>' result = result + self.section('AUTHOR', author) return result def docclass(self, object): """Produce text documentation for a given class object.""" name = object.__name__ bases = object.__bases__ title = 'class ' + self.bold(name) if bases: parents = map(lambda c, m=object.__module__: classname(c, m), bases) title = title + '(%s)' % join(parents, ', ') doc = getdoc(object) contents = doc and doc + '\n' methods = map(lambda (key, value): value, inspect.getmembers(object, inspect.ismethod)) for item in methods: contents = contents + '\n' + self.document(item) if not contents: return title + '\n' return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n' def docmethod(self, object): """Produce text documentation for a method object.""" return self.document(object.im_func) def formatvalue(self, object): """Format an argument default value as text.""" return '=' + self.repr(object) def docfunction(self, object): """Produce text documentation for a function object.""" try: args, varargs, varkw, defaults = inspect.getargspec(object) argspec = inspect.formatargspec( args, varargs, varkw, defaults, formatvalue=self.formatvalue) except TypeError: argspec = '(...)' if object.__name__ == '': decl = ' ' + argspec[1:-1] else: decl = self.bold(object.__name__) + argspec doc = getdoc(object) if doc: return decl + '\n' + rstrip(self.indent(doc)) + '\n' else: return decl + '\n' def docbuiltin(self, object): """Produce text documentation for a built-in function object.""" return (self.bold(object.__name__) + '(...)\n' + rstrip(self.indent(object.__doc__)) + '\n') # --------------------------------------------------------- user interfaces def pager(text): """The first time this is called, determine what kind of pager to use.""" global pager pager = getpager() pager(text) def getpager(): """Decide what method to use for paging through text.""" if type(sys.stdout) is not types.FileType: return plainpager if not sys.stdin.isatty() or not sys.stdout.isatty(): return plainpager if os.environ.has_key('PAGER'): return lambda a: pipepager(a, os.environ['PAGER']) if sys.platform in ['win', 'win32', 'nt']: return lambda a: tempfilepager(a, 'more') if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0: return lambda a: pipepager(a, 'less') import tempfile filename = tempfile.mktemp() open(filename, 'w').close() try: if hasattr(os, 'system') and os.system('more %s' % filename) == 0: return lambda text: pipepager(text, 'more') else: return ttypager finally: os.unlink(filename) def pipepager(text, cmd): """Page through text by feeding it to another program.""" pipe = os.popen(cmd, 'w') try: pipe.write(text) pipe.close() except IOError: # Ignore broken pipes caused by quitting the pager program. pass def tempfilepager(text, cmd): """Page through text by invoking a program on a temporary file.""" import tempfile filename = tempfile.mktemp() file = open(filename, 'w') file.write(text) file.close() try: os.system(cmd + ' ' + filename) finally: os.unlink(filename) def plain(text): """Remove boldface formatting from text.""" return re.sub('.\b', '', text) def ttypager(text): """Page through text on a text terminal.""" lines = split(plain(text), '\n') try: import tty fd = sys.stdin.fileno() old = tty.tcgetattr(fd) tty.setcbreak(fd) getchar = lambda: sys.stdin.read(1) except ImportError: tty = None getchar = lambda: sys.stdin.readline()[:-1][:1] try: r = inc = os.environ.get('LINES', 25) - 1 sys.stdout.write(join(lines[:inc], '\n') + '\n') while lines[r:]: sys.stdout.write('-- more --') sys.stdout.flush() c = getchar() if c in ['q', 'Q']: sys.stdout.write('\r \r') break elif c in ['\r', '\n']: sys.stdout.write('\r \r' + lines[r] + '\n') r = r + 1 continue if c in ['b', 'B', '\x1b']: r = r - inc - inc if r < 0: r = 0 sys.stdout.write('\n' + join(lines[r:r+inc], '\n') + '\n') r = r + inc finally: if tty: tty.tcsetattr(fd, tty.TCSAFLUSH, old) def plainpager(text): """Simply print unformatted text. This is the ultimate fallback.""" sys.stdout.write(plain(text)) def describe(thing): """Produce a short description of the given kind of thing.""" if inspect.ismodule(thing): if thing.__name__ in sys.builtin_module_names: return 'built-in module ' + thing.__name__ if hasattr(thing, '__path__'): return 'package ' + thing.__name__ else: return 'module ' + thing.__name__ if inspect.isbuiltin(thing): return 'built-in function ' + thing.__name__ if inspect.isclass(thing): return 'class ' + thing.__name__ if inspect.isfunction(thing): return 'function ' + thing.__name__ if inspect.ismethod(thing): return 'method ' + thing.__name__ return repr(thing) def locate(path): """Locate an object by name (or dotted path), importing as necessary.""" if not path: # special case: imp.find_module('') strangely succeeds return None, None if type(path) is not types.StringType: return None, path if hasattr(__builtins__, path): return None, getattr(__builtins__, path) parts = split(path, '.') n = 1 while n <= len(parts): path = join(parts[:n], '.') try: module = __import__(path) module = reload(module) except: # Did the error occur before or after we found the module? if sys.modules.has_key(path): filename = sys.modules[path].__file__ elif sys.exc_type is SyntaxError: filename = sys.exc_value.filename else: # module not found, so stop looking break # error occurred in the imported module, so report it raise DocImportError(filename, sys.exc_type, sys.exc_value) try: x = module for p in parts[1:]: x = getattr(x, p) return join(parts[:-1], '.'), x except AttributeError: n = n + 1 continue return None, None # --------------------------------------- interactive interpreter interface text = TextDoc() html = HTMLDoc() def doc(thing): """Display documentation on an object (for interactive use).""" if type(thing) is type(""): try: path, x = locate(thing) except DocImportError, value: print 'problem in %s - %s' % (value.filename, value.args) return if x: thing = x else: print 'could not find or import %s' % repr(thing) return desc = describe(thing) module = inspect.getmodule(thing) if module and module is not thing: desc = desc + ' in module ' + module.__name__ pager('Help on %s:\n\n' % desc + text.document(thing)) def writedocs(path, pkgpath=''): if os.path.isdir(path): dir = path for file in os.listdir(dir): path = os.path.join(dir, file) if os.path.isdir(path): writedocs(path, file + '.' + pkgpath) if os.path.isfile(path): writedocs(path, pkgpath) if os.path.isfile(path): modname = modulename(path) if modname: writedoc(pkgpath + modname) def writedoc(key): """Write HTML documentation to a file in the current directory.""" path, object = locate(key) if object: file = open(key + '.html', 'w') file.write(html.page(object)) file.close() print 'wrote', key + '.html' class Helper: def __repr__(self): return """To get help on a Python object, call help(object). To get help on a module or package, either import it before calling help(module) or call help('modulename').""" def __call__(self, *args): if args: doc(args[0]) else: print repr(self) help = Helper() def man(key): """Display documentation on an object in a form similar to man(1).""" path, object = locate(key) if object: title = 'Python Library Documentation: ' + describe(object) if path: title = title + ' in ' + path pager('\n' + title + '\n\n' + text.document(object)) found = 1 else: print 'could not find or import %s' % repr(key) def apropos(key): """Print all the one-line module summaries that contain a substring.""" key = lower(key) for module in sys.builtin_module_names: desc = __import__(module).__doc__ or '' desc = split(desc, '\n')[0] if find(lower(module + ' ' + desc), key) >= 0: print module, '-', desc or '(no description)' modules = [] for dir in pathdirs(): for module, desc in index(dir): desc = desc or '' if module not in modules: modules.append(module) if find(lower(module + ' ' + desc), key) >= 0: desc = desc or '(no description)' if module[-9:] == '.__init__': print module[:-9], '(package) -', desc else: print module, '-', desc # --------------------------------------------------- web browser interface def serve(address, callback=None): import BaseHTTPServer, mimetools # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded. class Message(mimetools.Message): def __init__(self, fp, seekable=1): Message = self.__class__ Message.__bases__[0].__bases__[0].__init__(self, fp, seekable) self.encodingheader = self.getheader('content-transfer-encoding') self.typeheader = self.getheader('content-type') self.parsetype() self.parseplist() class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler): def send_document(self, title, contents): self.send_response(200) self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write( ''' Python: %s''' % title) self.wfile.write(contents) self.wfile.write('') def do_GET(self): path = self.path if path[-5:] == '.html': path = path[:-5] if path[:1] == '/': path = path[1:] if path and path != '.': try: p, x = locate(path) except DocImportError, value: self.send_document(path, html.escape( 'problem with %s - %s' % (value.filename, value.args))) return if x: self.send_document(describe(x), html.document(x)) else: self.send_document(path, 'There is no Python module or object named "%s".' % path) else: heading = html.heading( '
 ' 'Python: Index of Modules' '', '#ffffff', '#7799ee') builtins = [] for name in sys.builtin_module_names: builtins.append('%s' % (name, name)) indices = ['

Built-in modules: ' + join(builtins, ', ')] seen = {} for dir in pathdirs(): indices.append(html.index(dir, seen)) self.send_document('Index of Modules', heading + join(indices)) def log_message(self, *args): pass class DocServer(BaseHTTPServer.HTTPServer): def __init__(self, address, callback): self.callback = callback self.base.__init__(self, address, self.handler) def server_activate(self): self.base.server_activate(self) if self.callback: self.callback() DocServer.base = BaseHTTPServer.HTTPServer DocServer.handler = DocHandler DocHandler.MessageClass = Message try: DocServer(address, callback).serve_forever() except KeyboardInterrupt: print 'server stopped' # -------------------------------------------------- command-line interface if __name__ == '__main__': import getopt class BadUsage: pass try: opts, args = getopt.getopt(sys.argv[1:], 'k:p:w') writing = 0 for opt, val in opts: if opt == '-k': apropos(lower(val)) break if opt == '-p': try: port = int(val) except ValueError: raise BadUsage def ready(port=port): print 'server ready at http://127.0.0.1:%d/' % port serve(('127.0.0.1', port), ready) break if opt == '-w': if not args: raise BadUsage writing = 1 else: if args: for arg in args: try: if os.path.isfile(arg): arg = importfile(arg) if writing: if os.path.isdir(arg): writedocs(arg) else: writedoc(arg) else: man(arg) except DocImportError, value: print 'problem in %s - %s' % ( value.filename, value.args) else: if sys.platform in ['mac', 'win', 'win32', 'nt']: # GUI platforms with threading import threading ready = threading.Event() address = ('127.0.0.1', 12346) threading.Thread( target=serve, args=(address, ready.set)).start() ready.wait() import webbrowser webbrowser.open('http://127.0.0.1:12346/') else: raise BadUsage except (getopt.error, BadUsage): print """%s ... Show documentation on something. may be the name of a Python function, module, or package, or a dotted reference to a class or function within a module or module in a package, or the filename of a Python module to import. %s -k Search for a keyword in the synopsis lines of all modules. %s -p Start an HTTP server on the given port on the local machine. %s -w ... Write out the HTML documentation for a module to a file. %s -w Write out the HTML documentation for all modules in the tree under a given directory to files in the current directory. """ % ((sys.argv[0],) * 5)