Kaydet (Commit) dec96e92 authored tarafından Ka-Ping Yee's avatar Ka-Ping Yee

Make force-loading optional; don't force-load in interactive mode.

Make synopsis() load modules as '__temp__' so they don't clobber anything.
Change "constants" section to "data" section.
Don't show __builtins__ or __doc__ in "data" section.
For Bob Weiner: don't boldface text in Emacs shells or dumb terminals.
Remove Helper.__repr__ (it really belongs in site.py, and should be                 guarded by a check for len(inspect.stack) <= 2).
üst 202c99b2
...@@ -53,36 +53,6 @@ from string import expandtabs, find, join, lower, split, strip, rfind, rstrip ...@@ -53,36 +53,6 @@ from string import expandtabs, find, join, lower, split, strip, rfind, rstrip
# --------------------------------------------------------- common routines # --------------------------------------------------------- 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:
info = inspect.getmoduleinfo(filename)
file = open(filename)
if info and 'b' in info[2]: # binary modules have to be imported
try: module = imp.load_module(info[0], file, filename, info[1:])
except: return None
result = split(module.__doc__ or '', '\n')[0]
else: # text modules can be directly examined
line = file.readline()
while line[:1] == '#' or not strip(line):
line = file.readline()
if not line: break
line = strip(line)
if line[:4] == 'r"""': line = line[1:]
if line[:3] == '"""':
line = line[3:]
if line[-1:] == '\\': line = line[:-1]
while not strip(line):
line = file.readline()
if not line: break
result = strip(split(line, '"""')[0])
else: result = None
file.close()
cache[filename] = (mtime, result)
return result
def pathdirs(): def pathdirs():
"""Convert sys.path into a list of absolute, existing, unique paths.""" """Convert sys.path into a list of absolute, existing, unique paths."""
dirs = [] dirs = []
...@@ -116,12 +86,11 @@ def classname(object, modname): ...@@ -116,12 +86,11 @@ def classname(object, modname):
name = object.__module__ + '.' + name name = object.__module__ + '.' + name
return name return name
def isconstant(object): def isdata(object):
"""Check if an object is of a type that probably means it's a constant.""" """Check if an object is of a type that probably means it's data."""
return type(object) in [ return not (inspect.ismodule(object) or inspect.isclass(object) or
types.FloatType, types.IntType, types.ListType, types.LongType, inspect.isroutine(object) or inspect.isframe(object) or
types.StringType, types.TupleType, types.TypeType, inspect.istraceback(object) or inspect.iscode(object))
hasattr(types, 'UnicodeType') and types.UnicodeType or 0]
def replace(text, *pairs): def replace(text, *pairs):
"""Do a series of global replacements on a string.""" """Do a series of global replacements on a string."""
...@@ -156,6 +125,46 @@ def allmethods(cl): ...@@ -156,6 +125,46 @@ def allmethods(cl):
methods[key] = getattr(cl, key) methods[key] = getattr(cl, key)
return methods return methods
# ----------------------------------------------------- module manipulation
def ispackage(path):
"""Guess whether a path refers to a package directory."""
if os.path.isdir(path):
for ext in ['.py', '.pyc', '.pyo']:
if os.path.isfile(os.path.join(path, '__init__' + ext)):
return 1
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:
info = inspect.getmoduleinfo(filename)
file = open(filename)
if info and 'b' in info[2]: # binary modules have to be imported
try: module = imp.load_module('__temp__', file, filename, info[1:])
except: return None
result = split(module.__doc__ or '', '\n')[0]
del sys.modules['__temp__']
else: # text modules can be directly examined
line = file.readline()
while line[:1] == '#' or not strip(line):
line = file.readline()
if not line: break
line = strip(line)
if line[:4] == 'r"""': line = line[1:]
if line[:3] == '"""':
line = line[3:]
if line[-1:] == '\\': line = line[:-1]
while not strip(line):
line = file.readline()
if not line: break
result = strip(split(line, '"""')[0])
else: result = None
file.close()
cache[filename] = (mtime, result)
return result
class ErrorDuringImport(Exception): class ErrorDuringImport(Exception):
"""Errors that occurred while trying to import something to document it.""" """Errors that occurred while trying to import something to document it."""
def __init__(self, filename, (exc, value, tb)): def __init__(self, filename, (exc, value, tb)):
...@@ -189,12 +198,49 @@ def importfile(path): ...@@ -189,12 +198,49 @@ def importfile(path):
file.close() file.close()
return module return module
def ispackage(path): def safeimport(path, forceload=0, cache={}):
"""Guess whether a path refers to a package directory.""" """Import a module; handle errors; return None if the module isn't found.
if os.path.isdir(path):
for ext in ['.py', '.pyc', '.pyo']: If the module *is* found but an exception occurs, it's wrapped in an
if os.path.isfile(os.path.join(path, '__init__' + ext)): ErrorDuringImport exception and reraised. Unlike __import__, if a
return 1 package path is specified, the module at the end of the path is returned,
not the package at the beginning. If the optional 'forceload' argument
is 1, we reload the module from disk (unless it's a dynamic extension)."""
if forceload and sys.modules.has_key(path):
# This is the only way to be sure. Checking the mtime of the file
# isn't good enough (e.g. what if the module contains a class that
# inherits from another module that has changed?).
if path not in sys.builtin_module_names:
# Python never loads a dynamic extension a second time from the
# same path, even if the file is changed or missing. Deleting
# the entry in sys.modules doesn't help for dynamic extensions,
# so we're not even going to try to keep them up to date.
info = inspect.getmoduleinfo(sys.modules[path].__file__)
if info[3] != imp.C_EXTENSION:
cache[path] = sys.modules[path] # prevent module from clearing
del sys.modules[path]
try:
module = __import__(path)
except:
# Did the error occur before or after the module was found?
(exc, value, tb) = info = sys.exc_info()
if sys.modules.has_key(path):
# An error occured while executing the imported module.
raise ErrorDuringImport(sys.modules[path].__file__, info)
elif exc is SyntaxError:
# A SyntaxError occurred before we could execute the module.
raise ErrorDuringImport(value.filename, info)
elif exc is ImportError and \
split(lower(str(value)))[:2] == ['no', 'module']:
# The module was not found.
return None
else:
# Some other error occurred during the importing process.
raise ErrorDuringImport(path, sys.exc_info())
for part in split(path, '.')[1:]:
try: module = getattr(module, part)
except AttributeError: return None
return module
# ---------------------------------------------------- formatter base class # ---------------------------------------------------- formatter base class
...@@ -221,7 +267,8 @@ class HTMLRepr(Repr): ...@@ -221,7 +267,8 @@ class HTMLRepr(Repr):
"""Class for safely making an HTML representation of a Python object.""" """Class for safely making an HTML representation of a Python object."""
def __init__(self): def __init__(self):
Repr.__init__(self) Repr.__init__(self)
self.maxlist = self.maxtuple = self.maxdict = 10 self.maxlist = self.maxtuple = 20
self.maxdict = 10
self.maxstring = self.maxother = 100 self.maxstring = self.maxother = 100
def escape(self, text): def escape(self, text):
...@@ -344,12 +391,11 @@ TT { font-family: lucida console, lucida typewriter, courier } ...@@ -344,12 +391,11 @@ TT { font-family: lucida console, lucida typewriter, courier }
def classlink(self, object, modname): def classlink(self, object, modname):
"""Make a link for a class.""" """Make a link for a class."""
name = classname(object, modname) name, module = object.__name__, sys.modules.get(object.__module__)
if sys.modules.has_key(object.__module__) and \ if hasattr(module, name) and getattr(module, name) is object:
getattr(sys.modules[object.__module__], object.__name__) is object:
return '<a href="%s.html#%s">%s</a>' % ( return '<a href="%s.html#%s">%s</a>' % (
object.__module__, object.__name__, name) module.__name__, name, classname(object, modname))
return name return classname(object, modname)
def modulelink(self, object): def modulelink(self, object):
"""Make a link for a module.""" """Make a link for a module."""
...@@ -475,9 +521,10 @@ TT { font-family: lucida console, lucida typewriter, courier } ...@@ -475,9 +521,10 @@ TT { font-family: lucida console, lucida typewriter, courier }
funcs.append((key, value)) funcs.append((key, value))
fdict[key] = '#-' + key fdict[key] = '#-' + key
if inspect.isfunction(value): fdict[value] = fdict[key] if inspect.isfunction(value): fdict[value] = fdict[key]
constants = [] data = []
for key, value in inspect.getmembers(object, isconstant): for key, value in inspect.getmembers(object, isdata):
constants.append((key, value)) if key not in ['__builtins__', '__doc__']:
data.append((key, value))
doc = self.markup(getdoc(object), self.preformat, fdict, cdict) doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
doc = doc and '<tt>%s</tt>' % doc doc = doc and '<tt>%s</tt>' % doc
...@@ -518,12 +565,12 @@ TT { font-family: lucida console, lucida typewriter, courier } ...@@ -518,12 +565,12 @@ TT { font-family: lucida console, lucida typewriter, courier }
contents.append(self.document(value, key, name, fdict, cdict)) contents.append(self.document(value, key, name, fdict, cdict))
result = result + self.bigsection( result = result + self.bigsection(
'Functions', '#ffffff', '#eeaa77', join(contents)) 'Functions', '#ffffff', '#eeaa77', join(contents))
if constants: if data:
contents = [] contents = []
for key, value in constants: for key, value in data:
contents.append(self.document(value, key)) contents.append(self.document(value, key))
result = result + self.bigsection( result = result + self.bigsection(
'Constants', '#ffffff', '#55aa55', join(contents, '<br>')) 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))
if hasattr(object, '__author__'): if hasattr(object, '__author__'):
contents = self.markup(str(object.__author__), self.preformat) contents = self.markup(str(object.__author__), self.preformat)
result = result + self.bigsection( result = result + self.bigsection(
...@@ -665,7 +712,8 @@ class TextRepr(Repr): ...@@ -665,7 +712,8 @@ class TextRepr(Repr):
"""Class for safely making a text representation of a Python object.""" """Class for safely making a text representation of a Python object."""
def __init__(self): def __init__(self):
Repr.__init__(self) Repr.__init__(self)
self.maxlist = self.maxtuple = self.maxdict = 10 self.maxlist = self.maxtuple = 20
self.maxdict = 10
self.maxstring = self.maxother = 100 self.maxstring = self.maxother = 100
def repr1(self, x, level): def repr1(self, x, level):
...@@ -754,9 +802,10 @@ class TextDoc(Doc): ...@@ -754,9 +802,10 @@ class TextDoc(Doc):
for key, value in inspect.getmembers(object, inspect.isroutine): for key, value in inspect.getmembers(object, inspect.isroutine):
if inspect.isbuiltin(value) or inspect.getmodule(value) is object: if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
funcs.append((key, value)) funcs.append((key, value))
constants = [] data = []
for key, value in inspect.getmembers(object, isconstant): for key, value in inspect.getmembers(object, isdata):
constants.append((key, value)) if key not in ['__builtins__', '__doc__']:
data.append((key, value))
if hasattr(object, '__path__'): if hasattr(object, '__path__'):
modpkgs = [] modpkgs = []
...@@ -785,11 +834,11 @@ class TextDoc(Doc): ...@@ -785,11 +834,11 @@ class TextDoc(Doc):
contents.append(self.document(value, key, name)) contents.append(self.document(value, key, name))
result = result + self.section('FUNCTIONS', join(contents, '\n')) result = result + self.section('FUNCTIONS', join(contents, '\n'))
if constants: if data:
contents = [] contents = []
for key, value in constants: for key, value in data:
contents.append(self.docother(value, key, name, 70)) contents.append(self.docother(value, key, name, 70))
result = result + self.section('CONSTANTS', join(contents, '\n')) result = result + self.section('DATA', join(contents, '\n'))
if hasattr(object, '__version__'): if hasattr(object, '__version__'):
version = str(object.__version__) version = str(object.__version__)
...@@ -903,13 +952,15 @@ def getpager(): ...@@ -903,13 +952,15 @@ def getpager():
return plainpager return plainpager
if os.environ.has_key('PAGER'): if os.environ.has_key('PAGER'):
if sys.platform == 'win32': # pipes completely broken in Windows if sys.platform == 'win32': # pipes completely broken in Windows
return lambda a: tempfilepager(a, os.environ['PAGER']) return lambda text: tempfilepager(plain(text), os.environ['PAGER'])
elif os.environ.get('TERM') in ['dumb', 'emacs']:
return lambda text: pipepager(plain(text), os.environ['PAGER'])
else: else:
return lambda a: pipepager(a, os.environ['PAGER']) return lambda text: pipepager(text, os.environ['PAGER'])
if sys.platform == 'win32': if sys.platform == 'win32':
return lambda a: tempfilepager(a, 'more <') return lambda text: tempfilepager(plain(text), 'more <')
if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0: if hasattr(os, 'system') and os.system('less 2>/dev/null') == 0:
return lambda a: pipepager(a, 'less') return lambda text: pipepager(text, 'less')
import tempfile import tempfile
filename = tempfile.mktemp() filename = tempfile.mktemp()
...@@ -922,6 +973,10 @@ def getpager(): ...@@ -922,6 +973,10 @@ def getpager():
finally: finally:
os.unlink(filename) os.unlink(filename)
def plain(text):
"""Remove boldface formatting from text."""
return re.sub('.\b', '', text)
def pipepager(text, cmd): def pipepager(text, cmd):
"""Page through text by feeding it to another program.""" """Page through text by feeding it to another program."""
pipe = os.popen(cmd, 'w') pipe = os.popen(cmd, 'w')
...@@ -943,10 +998,6 @@ def tempfilepager(text, cmd): ...@@ -943,10 +998,6 @@ def tempfilepager(text, cmd):
finally: finally:
os.unlink(filename) os.unlink(filename)
def plain(text):
"""Remove boldface formatting from text."""
return re.sub('.\b', '', text)
def ttypager(text): def ttypager(text):
"""Page through text on a text terminal.""" """Page through text on a text terminal."""
lines = split(plain(text), '\n') lines = split(plain(text), '\n')
...@@ -1010,49 +1061,12 @@ def describe(thing): ...@@ -1010,49 +1061,12 @@ def describe(thing):
return 'instance of ' + thing.__class__.__name__ return 'instance of ' + thing.__class__.__name__
return type(thing).__name__ return type(thing).__name__
def freshimport(path, cache={}): def locate(path, forceload=0):
"""Import a module freshly from disk, making sure it's up to date."""
if sys.modules.has_key(path):
# This is the only way to be sure. Checking the mtime of the file
# isn't good enough (e.g. what if the module contains a class that
# inherits from another module that has changed?).
if path not in sys.builtin_module_names:
# Python never loads a dynamic extension a second time from the
# same path, even if the file is changed or missing. Deleting
# the entry in sys.modules doesn't help for dynamic extensions,
# so we're not even going to try to keep them up to date.
info = inspect.getmoduleinfo(sys.modules[path].__file__)
if info[3] != imp.C_EXTENSION:
del sys.modules[path]
try:
module = __import__(path)
except:
# Did the error occur before or after the module was found?
(exc, value, tb) = info = sys.exc_info()
if sys.modules.has_key(path):
# An error occured while executing the imported module.
raise ErrorDuringImport(sys.modules[path].__file__, info)
elif exc is SyntaxError:
# A SyntaxError occurred before we could execute the module.
raise ErrorDuringImport(value.filename, info)
elif exc is ImportError and \
split(lower(str(value)))[:2] == ['no', 'module']:
# The module was not found.
return None
else:
# Some other error occurred during the importing process.
raise ErrorDuringImport(path, sys.exc_info())
for part in split(path, '.')[1:]:
try: module = getattr(module, part)
except AttributeError: return None
return module
def locate(path):
"""Locate an object by name or dotted path, importing as necessary.""" """Locate an object by name or dotted path, importing as necessary."""
parts = split(path, '.') parts = split(path, '.')
module, n = None, 0 module, n = None, 0
while n < len(parts): while n < len(parts):
nextmodule = freshimport(join(parts[:n+1], '.')) nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
if nextmodule: module, n = nextmodule, n + 1 if nextmodule: module, n = nextmodule, n + 1
else: break else: break
if module: if module:
...@@ -1071,12 +1085,12 @@ def locate(path): ...@@ -1071,12 +1085,12 @@ def locate(path):
text = TextDoc() text = TextDoc()
html = HTMLDoc() html = HTMLDoc()
def doc(thing, title='Python Library Documentation: %s'): def doc(thing, title='Python Library Documentation: %s', forceload=0):
"""Display text documentation, given an object or a path to an object.""" """Display text documentation, given an object or a path to an object."""
suffix, name = '', None suffix, name = '', None
if type(thing) is type(''): if type(thing) is type(''):
try: try:
object = locate(thing) object = locate(thing, forceload)
except ErrorDuringImport, value: except ErrorDuringImport, value:
print value print value
return return
...@@ -1094,10 +1108,10 @@ def doc(thing, title='Python Library Documentation: %s'): ...@@ -1094,10 +1108,10 @@ def doc(thing, title='Python Library Documentation: %s'):
suffix = ' in module ' + module.__name__ suffix = ' in module ' + module.__name__
pager(title % (desc + suffix) + '\n\n' + text.document(thing, name)) pager(title % (desc + suffix) + '\n\n' + text.document(thing, name))
def writedoc(key): def writedoc(key, forceload=0):
"""Write HTML documentation to a file in the current directory.""" """Write HTML documentation to a file in the current directory."""
try: try:
object = locate(key) object = locate(key, forceload)
except ErrorDuringImport, value: except ErrorDuringImport, value:
print value print value
else: else:
...@@ -1111,12 +1125,13 @@ def writedoc(key): ...@@ -1111,12 +1125,13 @@ def writedoc(key):
else: else:
print 'no Python documentation found for %s' % repr(key) print 'no Python documentation found for %s' % repr(key)
def writedocs(dir, pkgpath='', done={}): def writedocs(dir, pkgpath='', done=None):
"""Write out HTML documentation for all modules in a directory tree.""" """Write out HTML documentation for all modules in a directory tree."""
if done is None: done = {}
for file in os.listdir(dir): for file in os.listdir(dir):
path = os.path.join(dir, file) path = os.path.join(dir, file)
if ispackage(path): if ispackage(path):
writedocs(path, pkgpath + file + '.') writedocs(path, pkgpath + file + '.', done)
elif os.path.isfile(path): elif os.path.isfile(path):
modname = inspect.getmodulename(path) modname = inspect.getmodulename(path)
if modname: if modname:
...@@ -1251,15 +1266,20 @@ class Helper: ...@@ -1251,15 +1266,20 @@ class Helper:
if dir and os.path.isdir(os.path.join(dir, 'lib')): if dir and os.path.isdir(os.path.join(dir, 'lib')):
self.docdir = dir self.docdir = dir
def __repr__(self):
self()
return ''
def __call__(self, request=None): def __call__(self, request=None):
if request is not None: if request is not None:
self.help(request) self.help(request)
else: else:
self.intro() self.intro()
self.interact()
self.output.write('''
You're now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)". Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
''')
def interact(self):
self.output.write('\n') self.output.write('\n')
while 1: while 1:
self.output.write('help> ') self.output.write('help> ')
...@@ -1271,12 +1291,6 @@ class Helper: ...@@ -1271,12 +1291,6 @@ class Helper:
request = strip(replace(request, '"', '', "'", '')) request = strip(replace(request, '"', '', "'", ''))
if lower(request) in ['q', 'quit']: break if lower(request) in ['q', 'quit']: break
self.help(request) self.help(request)
self.output.write('''
You're now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)". Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
''')
def help(self, request): def help(self, request):
if type(request) is type(''): if type(request) is type(''):
...@@ -1361,8 +1375,8 @@ please set the environment variable PYTHONDOCS to indicate their location. ...@@ -1361,8 +1375,8 @@ please set the environment variable PYTHONDOCS to indicate their location.
self.output.write('could not read docs from %s\n' % filename) self.output.write('could not read docs from %s\n' % filename)
return return
divpat = re.compile('<div[^>]*navigat.*?</div[^>]*>', re.I | re.S) divpat = re.compile('<div[^>]*navigat.*?</div.*?>', re.I | re.S)
addrpat = re.compile('<address[^>]*>.*?</address[^>]*>', re.I | re.S) addrpat = re.compile('<address.*?>.*?</address.*?>', re.I | re.S)
document = re.sub(addrpat, '', re.sub(divpat, '', file.read())) document = re.sub(addrpat, '', re.sub(divpat, '', file.read()))
file.close() file.close()
...@@ -1460,7 +1474,7 @@ class ModuleScanner(Scanner): ...@@ -1460,7 +1474,7 @@ class ModuleScanner(Scanner):
if key is None: if key is None:
callback(None, modname, '') callback(None, modname, '')
else: else:
desc = split(freshimport(modname).__doc__ or '', '\n')[0] desc = split(__import__(modname).__doc__ or '', '\n')[0]
if find(lower(modname + ' - ' + desc), key) >= 0: if find(lower(modname + ' - ' + desc), key) >= 0:
callback(None, modname, desc) callback(None, modname, desc)
...@@ -1522,7 +1536,7 @@ def serve(port, callback=None, completer=None): ...@@ -1522,7 +1536,7 @@ def serve(port, callback=None, completer=None):
if path[:1] == '/': path = path[1:] if path[:1] == '/': path = path[1:]
if path and path != '.': if path and path != '.':
try: try:
obj = locate(path) obj = locate(path, forceload=1)
except ErrorDuringImport, value: except ErrorDuringImport, value:
self.send_document(path, html.escape(str(value))) self.send_document(path, html.escape(str(value)))
return return
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment