diff options
author | Karl Trygve Kalleberg <karltk@gentoo.org> | 2002-02-10 21:31:43 +0000 |
---|---|---|
committer | Karl Trygve Kalleberg <karltk@gentoo.org> | 2002-02-10 21:31:43 +0000 |
commit | 2d8dc89db84653d233bd97dc889f7f70c7a7c337 (patch) | |
tree | 0c8bcb868ecc906769c4a220ca46ad104ced3085 /dev-python | |
parent | libffi - foreign functions interface helper (diff) | |
download | gentoo-2-2d8dc89db84653d233bd97dc889f7f70c7a7c337.tar.gz gentoo-2-2d8dc89db84653d233bd97dc889f7f70c7a7c337.tar.bz2 gentoo-2-2d8dc89db84653d233bd97dc889f7f70c7a7c337.zip |
Python documentation tool for Python 2.0
Diffstat (limited to 'dev-python')
-rw-r--r-- | dev-python/pydoc/ChangeLog | 10 | ||||
-rw-r--r-- | dev-python/pydoc/files/inspect.py | 658 | ||||
-rw-r--r-- | dev-python/pydoc/files/pydoc.py | 1158 | ||||
-rw-r--r-- | dev-python/pydoc/pydoc-1.0.ebuild | 25 |
4 files changed, 1851 insertions, 0 deletions
diff --git a/dev-python/pydoc/ChangeLog b/dev-python/pydoc/ChangeLog new file mode 100644 index 000000000000..4fdbdfbc55b2 --- /dev/null +++ b/dev-python/pydoc/ChangeLog @@ -0,0 +1,10 @@ +# ChangeLog for dev-python/pydoc +# Copyright 2002 Gentoo Technologies, Inc.; Distributed under the GPL +# $Header: /var/cvsroot/gentoo-x86/dev-python/pydoc/ChangeLog,v 1.1 2002/02/10 21:31:43 karltk Exp $ + +*pydoc-1.0 ( 10 Feb 2002 ) + + 10 Feb 2002; Karl Trygve Kalleberg <karltk@gentoo.org> ChangeLog pydoc-1.0.ebuild files/pydoc.py files/inspect.py: + + Python documentation extractor, included in Python 2.1 and later. This one + is only for Python 2.0. diff --git a/dev-python/pydoc/files/inspect.py b/dev-python/pydoc/files/inspect.py new file mode 100644 index 000000000000..2d88bc1f6429 --- /dev/null +++ b/dev-python/pydoc/files/inspect.py @@ -0,0 +1,658 @@ +"""Get useful information from live Python objects. + +This module encapsulates the interface provided by the internal special +attributes (func_*, co_*, im_*, tb_*, etc.) in a friendlier fashion. +It also provides some help for examining source code and class layout. + +Here are some of the useful functions provided by this module: + + ismodule(), isclass(), ismethod(), isfunction(), istraceback(), + isframe(), iscode(), isbuiltin(), isroutine() - check object types + getmembers() - get members of an object that satisfy a given condition + + getfile(), getsourcefile(), getsource() - find an object's source code + getdoc(), getcomments() - get documentation on an object + getmodule() - determine the module that an object came from + getclasstree() - arrange classes so as to represent their hierarchy + + getargspec(), getargvalues() - get info about function arguments + formatargspec(), formatargvalues() - format an argument spec + getouterframes(), getinnerframes() - get info about frames + currentframe() - get the current stack frame + stack(), trace() - get info about frames on the stack or in a traceback +""" + +# This module is in the public domain. No warranties. + +__author__ = 'Ka-Ping Yee <ping@lfw.org>' +__date__ = '1 Jan 2001' + +import sys, os, types, string, re, dis, imp, tokenize + +# ----------------------------------------------------------- type-checking +def ismodule(object): + """Return true if the object is a module. + + Module objects provide these attributes: + __doc__ documentation string + __file__ filename (missing for built-in modules)""" + return type(object) is types.ModuleType + +def isclass(object): + """Return true if the object is a class. + + Class objects provide these attributes: + __doc__ documentation string + __module__ name of module in which this class was defined""" + return type(object) is types.ClassType or hasattr(object, '__bases__') + +def ismethod(object): + """Return true if the object is an instance method. + + Instance method objects provide these attributes: + __doc__ documentation string + __name__ name with which this method was defined + im_class class object in which this method belongs + im_func function object containing implementation of method + im_self instance to which this method is bound, or None""" + return type(object) is types.MethodType + +def isfunction(object): + """Return true if the object is a user-defined function. + + Function objects provide these attributes: + __doc__ documentation string + __name__ name with which this function was defined + func_code code object containing compiled function bytecode + func_defaults tuple of any default values for arguments + func_doc (same as __doc__) + func_globals global namespace in which this function was defined + func_name (same as __name__)""" + return type(object) in [types.FunctionType, types.LambdaType] + +def istraceback(object): + """Return true if the object is a traceback. + + Traceback objects provide these attributes: + tb_frame frame object at this level + tb_lasti index of last attempted instruction in bytecode + tb_lineno current line number in Python source code + tb_next next inner traceback object (called by this level)""" + return type(object) is types.TracebackType + +def isframe(object): + """Return true if the object is a frame object. + + Frame objects provide these attributes: + f_back next outer frame object (this frame's caller) + f_builtins built-in namespace seen by this frame + f_code code object being executed in this frame + f_exc_traceback traceback if raised in this frame, or None + f_exc_type exception type if raised in this frame, or None + f_exc_value exception value if raised in this frame, or None + f_globals global namespace seen by this frame + f_lasti index of last attempted instruction in bytecode + f_lineno current line number in Python source code + f_locals local namespace seen by this frame + f_restricted 0 or 1 if frame is in restricted execution mode + f_trace tracing function for this frame, or None""" + return type(object) is types.FrameType + +def iscode(object): + """Return true if the object is a code object. + + Code objects provide these attributes: + co_argcount number of arguments (not including * or ** args) + co_code string of raw compiled bytecode + co_consts tuple of constants used in the bytecode + co_filename name of file in which this code object was created + co_firstlineno number of first line in Python source code + co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg + co_lnotab encoded mapping of line numbers to bytecode indices + co_name name with which this code object was defined + co_names tuple of names of local variables + co_nlocals number of local variables + co_stacksize virtual machine stack space required + co_varnames tuple of names of arguments and local variables""" + return type(object) is types.CodeType + +def isbuiltin(object): + """Return true if the object is a built-in function or method. + + Built-in functions and methods provide these attributes: + __doc__ documentation string + __name__ original name of this function or method + __self__ instance to which a method is bound, or None""" + return type(object) is types.BuiltinFunctionType + +def isroutine(object): + """Return true if the object is any kind of function or method.""" + return isbuiltin(object) or isfunction(object) or ismethod(object) + +def getmembers(object, predicate=None): + """Return all members of an object as (name, value) pairs sorted by name. + Optionally, only return members that satisfy a given predicate.""" + results = [] + for key in dir(object): + value = getattr(object, key) + if not predicate or predicate(value): + results.append((key, value)) + results.sort() + return results + +# -------------------------------------------------- source code extraction +def indentsize(line): + """Return the indent size, in spaces, at the start of a line of text.""" + expline = string.expandtabs(line) + return len(expline) - len(string.lstrip(expline)) + +def getdoc(object): + """Get the documentation string for an object. + + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed.""" + if hasattr(object, '__doc__') and object.__doc__: + lines = string.split(string.expandtabs(object.__doc__), '\n') + margin = None + for line in lines[1:]: + content = len(string.lstrip(line)) + if not content: continue + indent = len(line) - content + if margin is None: margin = indent + else: margin = min(margin, indent) + if margin is not None: + for i in range(1, len(lines)): lines[i] = lines[i][margin:] + return string.join(lines, '\n') + +def getfile(object): + """Work out which source or compiled file an object was defined in.""" + if ismodule(object): + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError, 'arg is a built-in module' + if isclass(object): + object = sys.modules.get(object.__module__) + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError, 'arg is a built-in class' + if ismethod(object): + object = object.im_func + if isfunction(object): + object = object.func_code + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + return object.co_filename + raise TypeError, 'arg is not a module, class, method, ' \ + 'function, traceback, frame, or code object' + +def getmoduleinfo(path): + """Get the module name, suffix, mode, and module type for a given file.""" + filename = os.path.basename(path) + suffixes = map(lambda (suffix, mode, mtype): + (-len(suffix), suffix, mode, mtype), imp.get_suffixes()) + suffixes.sort() # try longest suffixes first, in case they overlap + for neglen, suffix, mode, mtype in suffixes: + if filename[neglen:] == suffix: + return filename[:neglen], suffix, mode, mtype + +def getmodulename(path): + """Return the module name for a given file, or None.""" + info = getmoduleinfo(path) + if info: return info[0] + +def getsourcefile(object): + """Return the Python source file an object was defined in, if it exists.""" + filename = getfile(object) + if string.lower(filename[-4:]) in ['.pyc', '.pyo']: + filename = filename[:-4] + '.py' + for suffix, mode, kind in imp.get_suffixes(): + if 'b' in mode and string.lower(filename[-len(suffix):]) == suffix: + # Looks like a binary file. We want to only return a text file. + return None + if os.path.exists(filename): + return filename + +def getabsfile(object): + """Return an absolute path to the source or compiled file for an object. + + The idea is for each object to have a unique origin, so this routine + normalizes the result as much as possible.""" + return os.path.normcase( + os.path.abspath(getsourcefile(object) or getfile(object))) + +modulesbyfile = {} + +def getmodule(object): + """Return the module an object was defined in, or None if not found.""" + if ismodule(object): + return object + if isclass(object): + return sys.modules.get(object.__module__) + try: + file = getabsfile(object) + except TypeError: + return None + if modulesbyfile.has_key(file): + return sys.modules[modulesbyfile[file]] + for module in sys.modules.values(): + if hasattr(module, '__file__'): + modulesbyfile[getabsfile(module)] = module.__name__ + if modulesbyfile.has_key(file): + return sys.modules[modulesbyfile[file]] + main = sys.modules['__main__'] + if hasattr(main, object.__name__): + mainobject = getattr(main, object.__name__) + if mainobject is object: + return main + builtin = sys.modules['__builtin__'] + if hasattr(builtin, object.__name__): + builtinobject = getattr(builtin, object.__name__) + if builtinobject is object: + return builtin + +def findsource(object): + """Return the entire source file and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of all the lines + in the file and the line number indexes a line in that list. An IOError + is raised if the source code cannot be retrieved.""" + try: + file = open(getsourcefile(object)) + except (TypeError, IOError): + raise IOError, 'could not get source code' + lines = file.readlines() + file.close() + + if ismodule(object): + return lines, 0 + + if isclass(object): + name = object.__name__ + pat = re.compile(r'^\s*class\s*' + name + r'\b') + for i in range(len(lines)): + if pat.match(lines[i]): return lines, i + else: raise IOError, 'could not find class definition' + + if ismethod(object): + object = object.im_func + if isfunction(object): + object = object.func_code + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + if not hasattr(object, 'co_firstlineno'): + raise IOError, 'could not find function definition' + lnum = object.co_firstlineno - 1 + pat = re.compile(r'^\s*def\s') + while lnum > 0: + if pat.match(lines[lnum]): break + lnum = lnum - 1 + return lines, lnum + +def getcomments(object): + """Get lines of comments immediately preceding an object's source code.""" + try: lines, lnum = findsource(object) + except IOError: return None + + if ismodule(object): + # Look for a comment block at the top of the file. + start = 0 + if lines and lines[0][:2] == '#!': start = 1 + while start < len(lines) and string.strip(lines[start]) in ['', '#']: + start = start + 1 + if start < len(lines) and lines[start][:1] == '#': + comments = [] + end = start + while end < len(lines) and lines[end][:1] == '#': + comments.append(string.expandtabs(lines[end])) + end = end + 1 + return string.join(comments, '') + + # Look for a preceding block of comments at the same indentation. + elif lnum > 0: + indent = indentsize(lines[lnum]) + end = lnum - 1 + if end >= 0 and string.lstrip(lines[end])[:1] == '#' and \ + indentsize(lines[end]) == indent: + comments = [string.lstrip(string.expandtabs(lines[end]))] + if end > 0: + end = end - 1 + comment = string.lstrip(string.expandtabs(lines[end])) + while comment[:1] == '#' and indentsize(lines[end]) == indent: + comments[:0] = [comment] + end = end - 1 + if end < 0: break + comment = string.lstrip(string.expandtabs(lines[end])) + while comments and string.strip(comments[0]) == '#': + comments[:1] = [] + while comments and string.strip(comments[-1]) == '#': + comments[-1:] = [] + return string.join(comments, '') + +class ListReader: + """Provide a readline() method to return lines from a list of strings.""" + def __init__(self, lines): + self.lines = lines + self.index = 0 + + def readline(self): + i = self.index + if i < len(self.lines): + self.index = i + 1 + return self.lines[i] + else: return '' + +class EndOfBlock(Exception): pass + +class BlockFinder: + """Provide a tokeneater() method to detect the end of a code block.""" + def __init__(self): + self.indent = 0 + self.started = 0 + self.last = 0 + + def tokeneater(self, type, token, (srow, scol), (erow, ecol), line): + if not self.started: + if type == tokenize.NAME: self.started = 1 + elif type == tokenize.NEWLINE: + self.last = srow + elif type == tokenize.INDENT: + self.indent = self.indent + 1 + elif type == tokenize.DEDENT: + self.indent = self.indent - 1 + if self.indent == 0: raise EndOfBlock, self.last + +def getblock(lines): + """Extract the block of code at the top of the given list of lines.""" + try: + tokenize.tokenize(ListReader(lines).readline, BlockFinder().tokeneater) + except EndOfBlock, eob: + return lines[:eob.args[0]] + +def getsourcelines(object): + """Return a list of source lines and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of the lines + corresponding to the object and the line number indicates where in the + original source file the first line of code was found. An IOError is + raised if the source code cannot be retrieved.""" + lines, lnum = findsource(object) + + if ismodule(object): return lines, 0 + else: return getblock(lines[lnum:]), lnum + 1 + +def getsource(object): + """Return the text of the source code for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a single string. An + IOError is raised if the source code cannot be retrieved.""" + lines, lnum = getsourcelines(object) + return string.join(lines, '') + +# --------------------------------------------------- class tree extraction +def walktree(classes, children, parent): + """Recursive helper function for getclasstree().""" + results = [] + classes.sort(lambda a, b: cmp(a.__name__, b.__name__)) + for c in classes: + results.append((c, c.__bases__)) + if children.has_key(c): + results.append(walktree(children[c], children, c)) + return results + +def getclasstree(classes, unique=0): + """Arrange the given list of classes into a hierarchy of nested lists. + + Where a nested list appears, it contains classes derived from the class + whose entry immediately precedes the list. Each entry is a 2-tuple + containing a class and a tuple of its base classes. If the 'unique' + argument is true, exactly one entry appears in the returned structure + for each class in the given list. Otherwise, classes using multiple + inheritance and their descendants will appear multiple times.""" + children = {} + roots = [] + for c in classes: + if c.__bases__: + for parent in c.__bases__: + if not children.has_key(parent): + children[parent] = [] + children[parent].append(c) + if unique and parent in classes: break + elif c not in roots: + roots.append(c) + for parent in children.keys(): + if parent not in classes: + roots.append(parent) + return walktree(roots, children, None) + +# ------------------------------------------------ argument list extraction +# These constants are from Python's compile.h. +CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 1, 2, 4, 8 + +def getargs(co): + """Get information about the arguments accepted by a code object. + + Three things are returned: (args, varargs, varkw), where 'args' is + a list of argument names (possibly containing nested lists), and + 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" + if not iscode(co): raise TypeError, 'arg is not a code object' + + code = co.co_code + nargs = co.co_argcount + names = co.co_varnames + args = list(names[:nargs]) + step = 0 + + # The following acrobatics are for anonymous (tuple) arguments. + for i in range(nargs): + if args[i][:1] in ['', '.']: + stack, remain, count = [], [], [] + while step < len(code): + op = ord(code[step]) + step = step + 1 + if op >= dis.HAVE_ARGUMENT: + opname = dis.opname[op] + value = ord(code[step]) + ord(code[step+1])*256 + step = step + 2 + if opname in ['UNPACK_TUPLE', 'UNPACK_SEQUENCE']: + remain.append(value) + count.append(value) + elif opname == 'STORE_FAST': + stack.append(names[value]) + remain[-1] = remain[-1] - 1 + while remain[-1] == 0: + remain.pop() + size = count.pop() + stack[-size:] = [stack[-size:]] + if not remain: break + remain[-1] = remain[-1] - 1 + if not remain: break + args[i] = stack[0] + + varargs = None + if co.co_flags & CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + return args, varargs, varkw + +def getargspec(func): + """Get the names and default values of a function's arguments. + + A tuple of four things is returned: (args, varargs, varkw, defaults). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'defaults' is an n-tuple of the default values of the last n arguments.""" + if not isfunction(func): raise TypeError, 'arg is not a Python function' + args, varargs, varkw = getargs(func.func_code) + return args, varargs, varkw, func.func_defaults + +def getargvalues(frame): + """Get information about arguments passed into a particular frame. + + A tuple of four things is returned: (args, varargs, varkw, locals). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'locals' is the locals dictionary of the given frame.""" + args, varargs, varkw = getargs(frame.f_code) + return args, varargs, varkw, frame.f_locals + +def joinseq(seq): + if len(seq) == 1: + return '(' + seq[0] + ',)' + else: + return '(' + string.join(seq, ', ') + ')' + +def strseq(object, convert, join=joinseq): + """Recursively walk a sequence, stringifying each element.""" + if type(object) in [types.ListType, types.TupleType]: + return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object)) + else: + return convert(object) + +def formatargspec(args, varargs=None, varkw=None, defaults=None, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargspec. + + The first four arguments are (args, varargs, varkw, defaults). The + other four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments.""" + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i in range(len(args)): + spec = strseq(args[i], formatarg, join) + if defaults and i >= firstdefault: + spec = spec + formatvalue(defaults[i - firstdefault]) + specs.append(spec) + if varargs: + specs.append(formatvarargs(varargs)) + if varkw: + specs.append(formatvarkw(varkw)) + return '(' + string.join(specs, ', ') + ')' + +def formatargvalues(args, varargs, varkw, locals, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargvalues. + + The first four arguments are (args, varargs, varkw, locals). The + next four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments.""" + def convert(name, locals=locals, + formatarg=formatarg, formatvalue=formatvalue): + return formatarg(name) + formatvalue(locals[name]) + specs = [] + for i in range(len(args)): + specs.append(strseq(args[i], convert, join)) + if varargs: + specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) + if varkw: + specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) + return '(' + string.join(specs, ', ') + ')' + +# -------------------------------------------------- stack frame extraction +def getframeinfo(frame, context=1): + """Get information about a frame or traceback object. + + A tuple of five things is returned: the filename, the line number of + the current line, the function name, a list of lines of context from + the source code, and the index of the current line within that list. + The optional second argument specifies the number of lines of context + to return, which are centered around the current line.""" + if istraceback(frame): + frame = frame.tb_frame + if not isframe(frame): + raise TypeError, 'arg is not a frame or traceback object' + + filename = getsourcefile(frame) + lineno = getlineno(frame) + if context > 0: + start = lineno - 1 - context/2 + try: + lines, lnum = findsource(frame) + except IOError: + lines = index = None + else: + start = max(start, 1) + start = min(start, len(lines) - context) + lines = lines[start:start+context] + index = lineno - 1 - start + else: + lines = index = None + + return (filename, lineno, frame.f_code.co_name, lines, index) + +def getlineno(frame): + """Get the line number from a frame object, allowing for optimization.""" + # Written by Marc-André Lemburg; revised by Jim Hugunin and Fredrik Lundh. + lineno = frame.f_lineno + code = frame.f_code + if hasattr(code, 'co_lnotab'): + table = code.co_lnotab + lineno = code.co_firstlineno + addr = 0 + for i in range(0, len(table), 2): + addr = addr + ord(table[i]) + if addr > frame.f_lasti: break + lineno = lineno + ord(table[i+1]) + return lineno + +def getouterframes(frame, context=1): + """Get a list of records for a frame and all higher (calling) frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + framelist = [] + while frame: + framelist.append((frame,) + getframeinfo(frame, context)) + frame = frame.f_back + return framelist + +def getinnerframes(tb, context=1): + """Get a list of records for a traceback's frame and all lower frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + framelist = [] + while tb: + framelist.append((tb.tb_frame,) + getframeinfo(tb, context)) + tb = tb.tb_next + return framelist + +def currentframe(): + """Return the frame object for the caller's stack frame.""" + try: + raise 'catch me' + except: + return sys.exc_traceback.tb_frame.f_back + +if hasattr(sys, '_getframe'): currentframe = sys._getframe + +def stack(context=1): + """Return a list of records for the stack above the caller's frame.""" + return getouterframes(currentframe().f_back, context) + +def trace(context=1): + """Return a list of records for the stack below the current exception.""" + return getinnerframes(sys.exc_traceback, context) diff --git a/dev-python/pydoc/files/pydoc.py b/dev-python/pydoc/files/pydoc.py new file mode 100644 index 000000000000..45e0966e3a2f --- /dev/null +++ b/dev-python/pydoc/files/pydoc.py @@ -0,0 +1,1158 @@ +#!/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 <name>" to show +documentation on something. <name> 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 <keyword>" to search for a keyword +in the one-line descriptions of modules. + +Or, at the shell prompt, run "pydoc -p <port>" 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 <name>" to write out the HTML +documentation for a module to a file named "<name>.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 <ping@lfw.org>" +__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'<font color="#c040c0">\1</font>', 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', '<br>\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 + '<td width="%d%%" valign=top>' % (100/cols) + for i in range(rows*col, rows*col+rows): + if i < len(list): + result = result + format(list[i]) + '<br>' + result = result + '</td>' + return '<table width="100%%"><tr>%s</tr></table>' % result + + def heading(self, title, fgcol, bgcol, extras=''): + """Format a page heading.""" + return """ +<p><table width="100%%" cellspacing=0 cellpadding=0 border=0> +<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small +><font color="%s" face="helvetica, arial"> %s</font></td +><td align=right valign=bottom +><font color="%s" face="helvetica, arial"> %s</font></td></tr></table> + """ % (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 = """ +<p><table width="100%%" cellspacing=0 cellpadding=0 border=0> +<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small +><font color="%s" face="helvetica, arial"> %s</font></td></tr> + """ % (bgcol, fgcol, title) + if prelude: + result = result + """ +<tr><td bgcolor="%s">%s</td> +<td bgcolor="%s" colspan=2>%s</td></tr> + """ % (bgcol, marginalia, bgcol, prelude) + result = result + """ +<tr><td bgcolor="%s">%s</td><td>%s</td> + """ % (bgcol, marginalia, gap) + + result = result + '<td width="100%%">%s</td></tr></table>' % contents + return result + + def bigsection(self, title, *args): + """Format a section with a big heading.""" + title = '<big><strong>%s</strong></big>' % title + return apply(self.section, (title,) + args) + + def footer(self): + return """ +<table width="100%"><tr><td align=right> +<font face="helvetica, arial"><small><small>generated with +<strong>htmldoc</strong> by Ka-Ping Yee</a></small></small></font> +</td></tr></table> + """ + + 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 '<a href="%s">%s</a>' % (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 '<a href="%s">%s</a>' % (dict[object], name) + return name + + def modulelink(self, object): + """Make a link for a module.""" + return '<a href="%s.html">%s</a>' % (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 '<font color="#909090">%s</font>' % name + if path: + url = '%s.%s.html' % (path, name) + else: + url = '%s.html' % name + if ispackage: + text = '<strong>%s</strong> (package)' % name + else: + text = name + return '<a href="%s">%s</a>' % (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('<a href="%s">%s</a>' % (url, escape(url))) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%s.txt' % rfcnum + results.append('<a href="%s">%s</a>' % (url, escape(rfc))) + else: + if text[end:end+1] == '(': + results.append(self.namelink(name, methods, funcs, classes)) + elif selfdot: + results.append('self.<strong>%s</strong>' % 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 + '<dt><font face="helvetica, arial"><small>' + 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</small></font></dt>' + elif type(entry) is type([]): + result = result + \ + '<dd>\n%s</dd>\n' % self.doctree(entry, modname, classes, c) + return '<dl>\n%s</dl>\n' % result + + def docmodule(self, object): + """Produce HTML documentation for a module object.""" + name = object.__name__ + result = '' + head = '<br><big><big><strong> %s</strong></big></big>' % name + try: + file = inspect.getsourcefile(object) + filelink = '<a href="file:%s">%s</a>' % (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', '<a href=".">index</a><br>' + 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 '<tt>%s</tt>' % doc + result = result + '<p><small>%s</small></p>\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 + ('<br><strong>%s</strong> = %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 = '<a name="%s">class <strong>%s</strong></a>' % (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 = '<small><tt>' + doc + '<br> </tt></small>' + 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 ('<small><font color="#909090">=%s</font></small>' % + 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__ == '<lambda>': + decl = '<em>lambda</em> ' + argspec[1:-1] + else: + anchor = clname + '-' + object.__name__ + decl = '<a name="%s"\n><strong>%s</strong>%s</a>\n' % ( + anchor, object.__name__, argspec) + doc = self.markup(getdoc(object), self.preformat, + funcs, classes, methods) + doc = replace(doc, ('<br>\n', '</tt></small\n><dd><small><tt>')) + doc = doc and '<tt>%s</tt>' % doc + return '<dl><dt>%s<dd><small>%s</small></dl>' % (decl, doc) + + def docbuiltin(self, object, *extras): + """Produce HTML documentation for a built-in function.""" + return '<dl><dt><strong>%s</strong>(...)</dl>' % object.__name__ + + def page(self, object): + """Produce a complete HTML page of documentation for an object.""" + return '''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html><title>Python: %s</title> +<body bgcolor="#ffffff"> +%s +</body></html> +''' % (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__ == '<lambda>': + decl = '<lambda> ' + 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( +'''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html><title>Python: %s</title><body bgcolor="#ffffff">''' % title) + self.wfile.write(contents) + self.wfile.write('</body></html>') + + 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( + '<br><big><big><strong> ' + 'Python: Index of Modules' + '</strong></big></big>', + '#ffffff', '#7799ee') + builtins = [] + for name in sys.builtin_module_names: + builtins.append('<a href="%s.html">%s</a>' % (name, name)) + indices = ['<p>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 <name> ... + Show documentation on something. + <name> 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 <keyword> + Search for a keyword in the synopsis lines of all modules. + +%s -p <port> + Start an HTTP server on the given port on the local machine. + +%s -w <module> ... + Write out the HTML documentation for a module to a file. + +%s -w <moduledir> + 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) diff --git a/dev-python/pydoc/pydoc-1.0.ebuild b/dev-python/pydoc/pydoc-1.0.ebuild new file mode 100644 index 000000000000..01aa86bbee42 --- /dev/null +++ b/dev-python/pydoc/pydoc-1.0.ebuild @@ -0,0 +1,25 @@ +# Copyright 1999-2001 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License, v2 or later +# Maintainer: Tools Team <tools@gentoo.org> +# Author: Karl Trygve Kalleberg <karltk@gentoo.org> +# $Header: /var/cvsroot/gentoo-x86/dev-python/pydoc/pydoc-1.0.ebuild,v 1.1 2002/02/10 21:31:43 karltk Exp $ + +S=${WORKDIR}/${P} +DESCRIPTION="Python documentation tool and module" +SRC_URI="" +HOMEPAGE="http://www.lfw.org/python" + +RDEPEND="=dev-lang/python-2.0*" + +src_install () { + dodir /usr/lib/python2.0/site-packages + + insinto /usr/lib/python2.0/site-packages + doins ${FILESDIR}/inspect.py + + exeinto /usr/lib/python2.0/site-packages + doexe ${FILESDIR}/pydoc.py + + dodir /usr/bin + dosym /usr/lib/python2.0/site-packages/pydoc.py /usr/bin/pydoc +} |