aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/pydoc.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/pydoc.py')
-rwxr-xr-xLib/pydoc.py1147
1 files changed, 833 insertions, 314 deletions
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 68ba21f30fc..37616fb3edd 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: latin-1 -*-
+#!/usr/bin/env python3
"""Generate Python documentation in HTML or text for interactive use.
In the Python interpreter, do "from pydoc import help" to provide online
@@ -16,28 +15,34 @@ backslash on Windows) it is treated as the path to a Python source file.
Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
of all available modules.
-Run "pydoc -p <port>" to start an HTTP server on a given port on the
-local machine to generate documentation web pages.
+Run "pydoc -p <port>" to start an HTTP server on the given port on the
+local machine. Port number 0 can be used to get an arbitrary unused port.
+
+Run "pydoc -b" to start an HTTP server on an arbitrary unused port and
+open a Web browser to interactively browse documentation. The -p option
+can be used with the -b option to explicitly specify the server port.
For platforms without a command line, "pydoc -g" starts the HTTP server
-and also pops up a little window for controlling it.
+and also pops up a little window for controlling it. This option is
+deprecated, since the server can now be controlled directly from HTTP
+clients.
Run "pydoc -w <name>" to write out the HTML documentation for a module
to a file named "<name>.html".
Module docs for core modules are assumed to be in
- http://docs.python.org/library/
+ http://docs.python.org/X.Y/library/
This can be overridden by setting the PYTHONDOCS environment variable
to a different URL or to a local directory containing the Library
Reference Manual pages.
"""
-
+__all__ = ['help']
__author__ = "Ka-Ping Yee <ping@lfw.org>"
__date__ = "26 February 2001"
-__version__ = "$Revision: 88564 $"
+__version__ = "$Revision$"
__credits__ = """Guido van Rossum, for an excellent programming language.
Tommy Burnette, the original creator of manpy.
Paul Prescod, for all his work on onlinehelp.
@@ -52,17 +57,22 @@ Richard Chamberlain, for the first implementation of textdoc.
# the current directory is changed with os.chdir(), an incorrect
# path will be displayed.
-import sys, imp, os, re, types, inspect, __builtin__, pkgutil, warnings
-from repr import Repr
-from string import expandtabs, find, join, lower, split, strip, rfind, rstrip
-from traceback import extract_tb
-try:
- from collections import deque
-except ImportError:
- # Python 2.3 compatibility
- class deque(list):
- def popleft(self):
- return self.pop(0)
+import builtins
+import imp
+import inspect
+import io
+import os
+import pkgutil
+import platform
+import re
+import sys
+import time
+import tokenize
+import warnings
+from collections import deque
+from reprlib import Repr
+from traceback import extract_tb, format_exception_only
+
# --------------------------------------------------------- common routines
@@ -81,16 +91,16 @@ def pathdirs():
def getdoc(object):
"""Get the doc string or comments for an object."""
result = inspect.getdoc(object) or inspect.getcomments(object)
- return result and re.sub('^ *\n', '', rstrip(result)) or ''
+ return result and re.sub('^ *\n', '', result.rstrip()) or ''
def splitdoc(doc):
"""Split a doc string into a synopsis line (if any) and the rest."""
- lines = split(strip(doc), '\n')
+ lines = doc.strip().split('\n')
if len(lines) == 1:
return lines[0], ''
- elif len(lines) >= 2 and not rstrip(lines[1]):
- return lines[0], join(lines[2:], '\n')
- return '', join(lines, '\n')
+ elif len(lines) >= 2 and not lines[1].rstrip():
+ return lines[0], '\n'.join(lines[2:])
+ return '', '\n'.join(lines)
def classname(object, modname):
"""Get a class name and qualify it with a module name if necessary."""
@@ -108,7 +118,7 @@ def isdata(object):
def replace(text, *pairs):
"""Do a series of global replacements on a string."""
while pairs:
- text = join(split(text, pairs[0]), pairs[1])
+ text = pairs[1].join(text.split(pairs[0]))
pairs = pairs[2:]
return text
@@ -160,13 +170,15 @@ def visiblename(name, all=None, obj=None):
"""Decide whether to show documentation on a variable."""
# Certain special names are redundant.
_hidden_names = ('__builtins__', '__doc__', '__file__', '__path__',
- '__module__', '__name__', '__slots__', '__package__')
+ '__module__', '__name__', '__slots__', '__package__',
+ '__cached__', '__author__', '__credits__', '__date__',
+ '__version__')
if name in _hidden_names: return 0
# Private names are hidden, but special names are displayed.
if name.startswith('__') and name.endswith('__'): return 1
# Namedtuples have public fields and methods with a single leading underscore
if name.startswith('_') and hasattr(obj, '_fields'):
- return 1
+ return True
if all is not None:
# only document that which the programmer exported in __all__
return name in all
@@ -175,12 +187,12 @@ def visiblename(name, all=None, obj=None):
def classify_class_attrs(object):
"""Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
- def fixup(data):
- name, kind, cls, value = data
+ results = []
+ for (name, kind, cls, value) in inspect.classify_class_attrs(object):
if inspect.isdatadescriptor(value):
kind = 'data descriptor'
- return name, kind, cls, value
- return map(fixup, inspect.classify_class_attrs(object))
+ results.append((name, kind, cls, value))
+ return results
# ----------------------------------------------------- module manipulation
@@ -194,18 +206,18 @@ def ispackage(path):
def source_synopsis(file):
line = file.readline()
- while line[:1] == '#' or not strip(line):
+ while line[:1] == '#' or not line.strip():
line = file.readline()
if not line: break
- line = strip(line)
+ line = line.strip()
if line[:4] == 'r"""': line = line[1:]
if line[:3] == '"""':
line = line[3:]
if line[-1:] == '\\': line = line[:-1]
- while not strip(line):
+ while not line.strip():
line = file.readline()
if not line: break
- result = strip(split(line, '"""')[0])
+ result = line.split('"""')[0].strip()
else: result = None
return result
@@ -216,7 +228,7 @@ def synopsis(filename, cache={}):
if lastupdate is None or lastupdate < mtime:
info = inspect.getmoduleinfo(filename)
try:
- file = open(filename)
+ file = tokenize.open(filename)
except IOError:
# module can't be opened, so skip it
return None
@@ -234,35 +246,28 @@ def synopsis(filename, cache={}):
class ErrorDuringImport(Exception):
"""Errors that occurred while trying to import something to document it."""
def __init__(self, filename, exc_info):
- exc, value, tb = exc_info
self.filename = filename
- self.exc = exc
- self.value = value
- self.tb = tb
+ self.exc, self.value, self.tb = exc_info
def __str__(self):
- exc = self.exc
- if type(exc) is types.ClassType:
- exc = exc.__name__
+ exc = self.exc.__name__
return 'problem in %s - %s: %s' % (self.filename, exc, self.value)
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 ErrorDuringImport(path, sys.exc_info())
- file.close()
+ with open(path, 'rb') as file:
+ if file.read(len(magic)) == magic:
+ kind = imp.PY_COMPILED
+ else:
+ kind = imp.PY_SOURCE
+ file.seek(0)
+ filename = os.path.basename(path)
+ name, ext = os.path.splitext(filename)
+ try:
+ module = imp.load_module(name, file, path, (ext, 'r', kind))
+ except:
+ raise ErrorDuringImport(path, sys.exc_info())
return module
def safeimport(path, forceload=0, cache={}):
@@ -280,12 +285,11 @@ def safeimport(path, forceload=0, cache={}):
# that inherits from another module that has changed).
if forceload and path in sys.modules:
if path not in sys.builtin_module_names:
- # Avoid simply calling reload() because it leaves names in
- # the currently loaded module lying around if they're not
- # defined in the new source file. Instead, remove the
- # module from sys.modules and re-import. Also remove any
- # submodules because they won't appear in the newly loaded
- # module's namespace if they're already in sys.modules.
+ # Remove the module from sys.modules and re-import to try
+ # and avoid problems with partially loaded modules.
+ # Also remove any submodules because they won't appear
+ # in the newly loaded module's namespace if they're already
+ # in sys.modules.
subs = [m for m in sys.modules if m.startswith(path + '.')]
for key in [path] + subs:
# Prevent garbage collection.
@@ -308,7 +312,7 @@ def safeimport(path, forceload=0, cache={}):
else:
# Some other error occurred during the importing process.
raise ErrorDuringImport(path, sys.exc_info())
- for part in split(path, '.')[1:]:
+ for part in path.split('.')[1:]:
try: module = getattr(module, part)
except AttributeError: return None
return module
@@ -316,6 +320,11 @@ def safeimport(path, forceload=0, cache={}):
# ---------------------------------------------------- formatter base class
class Doc:
+
+ PYTHONDOCS = os.environ.get("PYTHONDOCS",
+ "http://docs.python.org/%d.%d/library"
+ % sys.version_info[:2])
+
def document(self, object, name=None, *args):
"""Generate documentation for an object."""
args = (object, name) + args
@@ -338,7 +347,7 @@ class Doc:
"""Raise an exception for unimplemented types."""
message = "don't know how to document object%s of type %s" % (
name and ' ' + repr(name), type(object).__name__)
- raise TypeError, message
+ raise TypeError(message)
docmodule = docclass = docroutine = docother = docproperty = docdata = fail
@@ -350,14 +359,14 @@ class Doc:
except TypeError:
file = '(built-in)'
- docloc = os.environ.get("PYTHONDOCS",
- "http://docs.python.org/library")
+ docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS)
+
basedir = os.path.join(sys.exec_prefix, "lib",
- "python"+sys.version[0:3])
+ "python%d.%d" % sys.version_info[:2])
if (isinstance(object, type(os)) and
(object.__name__ in ('errno', 'exceptions', 'gc', 'imp',
'marshal', 'posix', 'signal', 'sys',
- 'thread', 'zipimport') or
+ '_thread', 'zipimport') or
(file.startswith(basedir) and
not file.startswith(os.path.join(basedir, 'site-packages')))) and
object.__name__ not in ('xml.etree', 'test.pydoc_mod')):
@@ -387,7 +396,7 @@ class HTMLRepr(Repr):
def repr1(self, x, level):
if hasattr(type(x), '__name__'):
- methodname = 'repr_' + join(split(type(x).__name__), '_')
+ methodname = 'repr_' + '_'.join(type(x).__name__.split())
if hasattr(self, methodname):
return getattr(self, methodname)(x, level)
return self.escape(cram(stripid(repr(x)), self.maxother))
@@ -424,9 +433,10 @@ class HTMLDoc(Doc):
def page(self, title, contents):
"""Format an HTML page."""
- return '''
+ return '''\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: %s</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body bgcolor="#f0f0f8">
%s
</body></html>''' % (title, contents)
@@ -471,7 +481,7 @@ class HTMLDoc(Doc):
def preformat(self, text):
"""Format literal preformatted text."""
- text = self.escape(expandtabs(text))
+ text = self.escape(text.expandtabs())
return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
' ', '&nbsp;', '\n', '<br>\n')
@@ -508,9 +518,9 @@ class HTMLDoc(Doc):
"""Make a link for a module."""
return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
- def modpkglink(self, data):
+ def modpkglink(self, modpkginfo):
"""Make a link for a module or package to display in an index."""
- name, path, ispackage, shadowed = data
+ name, path, ispackage, shadowed = modpkginfo
if shadowed:
return self.grey(name)
if path:
@@ -523,6 +533,10 @@ class HTMLDoc(Doc):
text = name
return '<a href="%s">%s</a>' % (url, text)
+ def filelink(self, url, path):
+ """Make a link to source file."""
+ return '<a href="file:%s">%s</a>' % (url, path)
+
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."""
@@ -557,7 +571,7 @@ class HTMLDoc(Doc):
results.append(self.namelink(name, classes))
here = end
results.append(escape(text[here:]))
- return join(results, '')
+ return ''.join(results)
# ---------------------------------------------- type-specific routines
@@ -573,7 +587,7 @@ class HTMLDoc(Doc):
parents = []
for base in bases:
parents.append(self.classlink(base, modname))
- result = result + '(' + join(parents, ', ') + ')'
+ result = result + '(' + ', '.join(parents) + ')'
result = result + '\n</font></dt>'
elif type(entry) is type([]):
result = result + '<dd>\n%s</dd>\n' % self.formattree(
@@ -587,13 +601,13 @@ class HTMLDoc(Doc):
all = object.__all__
except AttributeError:
all = None
- parts = split(name, '.')
+ parts = name.split('.')
links = []
for i in range(len(parts)-1):
links.append(
'<a href="%s.html"><font color="#ffffff">%s</font></a>' %
- (join(parts[:i+1], '.'), parts[i]))
- linkedname = join(links + parts[-1:], '.')
+ ('.'.join(parts[:i+1]), parts[i]))
+ linkedname = '.'.join(links + parts[-1:])
head = '<big><big><strong>%s</strong></big></big>' % linkedname
try:
path = inspect.getabsfile(object)
@@ -601,22 +615,22 @@ class HTMLDoc(Doc):
if sys.platform == 'win32':
import nturl2path
url = nturl2path.pathname2url(path)
- filelink = '<a href="file:%s">%s</a>' % (url, path)
+ filelink = self.filelink(url, path)
except TypeError:
filelink = '(built-in)'
info = []
if hasattr(object, '__version__'):
version = str(object.__version__)
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
- version = strip(version[11:-1])
+ version = version[11:-1].strip()
info.append('version %s' % self.escape(version))
if hasattr(object, '__date__'):
info.append(self.escape(str(object.__date__)))
if info:
- head = head + ' (%s)' % join(info, ', ')
+ head = head + ' (%s)' % ', '.join(info)
docloc = self.getdocloc(object)
if docloc is not None:
- docloc = '<br><a href="%(docloc)s">Module Docs</a>' % locals()
+ docloc = '<br><a href="%(docloc)s">Module Reference</a>' % locals()
else:
docloc = ''
result = self.heading(
@@ -669,30 +683,30 @@ class HTMLDoc(Doc):
'Package Contents', '#ffffff', '#aa55cc', contents)
elif modules:
contents = self.multicolumn(
- modules, lambda key_value, s=self: s.modulelink(key_value[1]))
+ modules, lambda t: self.modulelink(t[1]))
result = result + self.bigsection(
'Modules', '#ffffff', '#aa55cc', contents)
if classes:
- classlist = map(lambda key_value: key_value[1], classes)
+ classlist = [value for (key, value) in classes]
contents = [
self.formattree(inspect.getclasstree(classlist, 1), name)]
for key, value in classes:
contents.append(self.document(value, key, name, fdict, cdict))
result = result + self.bigsection(
- 'Classes', '#ffffff', '#ee77aa', join(contents))
+ 'Classes', '#ffffff', '#ee77aa', ' '.join(contents))
if funcs:
contents = []
for key, value in funcs:
contents.append(self.document(value, key, name, fdict, cdict))
result = result + self.bigsection(
- 'Functions', '#ffffff', '#eeaa77', join(contents))
+ 'Functions', '#ffffff', '#eeaa77', ' '.join(contents))
if data:
contents = []
for key, value in data:
contents.append(self.document(value, key))
result = result + self.bigsection(
- 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))
+ 'Data', '#ffffff', '#55aa55', '<br>\n'.join(contents))
if hasattr(object, '__author__'):
contents = self.markup(str(object.__author__), self.preformat)
result = result + self.bigsection(
@@ -768,8 +782,7 @@ class HTMLDoc(Doc):
push(msg)
for name, kind, homecls, value in ok:
base = self.docother(getattr(object, name), name, mod)
- if (hasattr(value, '__call__') or
- inspect.isdatadescriptor(value)):
+ if callable(value) or inspect.isdatadescriptor(value):
doc = getattr(value, "__doc__", None)
else:
doc = None
@@ -783,8 +796,10 @@ class HTMLDoc(Doc):
push('\n')
return attrs
- attrs = filter(lambda data: visiblename(data[0], obj=object),
- classify_class_attrs(object))
+ attrs = [(name, kind, cls, value)
+ for name, kind, cls, value in classify_class_attrs(object)
+ if visiblename(name, obj=object)]
+
mdict = {}
for key, kind, homecls, value in attrs:
mdict[key] = anchor = '#' + name + '-' + key
@@ -808,7 +823,7 @@ class HTMLDoc(Doc):
thisclass = attrs[0][2]
attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
- if thisclass is __builtin__.object:
+ if thisclass is builtins.object:
attrs = inherited
continue
elif thisclass is object:
@@ -819,10 +834,7 @@ class HTMLDoc(Doc):
tag += ':<br>\n'
# Sort attrs by name.
- try:
- attrs.sort(key=lambda t: t[0])
- except TypeError:
- attrs.sort(lambda t1, t2: cmp(t1[0], t2[0])) # 2.3 compat
+ attrs.sort(key=lambda t: t[0])
# Pump out the attrs, segregated by kind.
attrs = spill('Methods %s' % tag, attrs,
@@ -850,7 +862,7 @@ class HTMLDoc(Doc):
parents = []
for base in bases:
parents.append(self.classlink(base, object.__module__))
- title = title + '(%s)' % join(parents, ', ')
+ title = title + '(%s)' % ', '.join(parents)
doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)
doc = doc and '<tt>%s<br>&nbsp;</tt>' % doc
@@ -869,17 +881,17 @@ class HTMLDoc(Doc):
note = ''
skipdocs = 0
if inspect.ismethod(object):
- imclass = object.im_class
+ imclass = object.__self__.__class__
if cl:
if imclass is not cl:
note = ' from ' + self.classlink(imclass, mod)
else:
- if object.im_self is not None:
+ if object.__self__ is not None:
note = ' method of %s instance' % self.classlink(
- object.im_self.__class__, mod)
+ object.__self__.__class__, mod)
else:
note = ' unbound %s method' % self.classlink(imclass,mod)
- object = object.im_func
+ object = object.__func__
if name == realname:
title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
@@ -894,11 +906,17 @@ class HTMLDoc(Doc):
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
anchor, name, reallink)
if inspect.isfunction(object):
- args, varargs, varkw, defaults = inspect.getargspec(object)
+ args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \
+ inspect.getfullargspec(object)
argspec = inspect.formatargspec(
- args, varargs, varkw, defaults, formatvalue=self.formatvalue)
+ args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann,
+ formatvalue=self.formatvalue,
+ formatannotation=inspect.formatannotationrelativeto(object))
if realname == '<lambda>':
title = '<strong>%s</strong> <em>lambda</em> ' % name
+ # XXX lambda's won't usually have func_annotations['return']
+ # since the syntax doesn't support but it is possible.
+ # So removing parentheses isn't truly safe.
argspec = argspec[1:-1] # remove parentheses
else:
argspec = '(...)'
@@ -964,7 +982,7 @@ class TextRepr(Repr):
def repr1(self, x, level):
if hasattr(type(x), '__name__'):
- methodname = 'repr_' + join(split(type(x).__name__), '_')
+ methodname = 'repr_' + '_'.join(type(x).__name__.split())
if hasattr(self, methodname):
return getattr(self, methodname)(x, level)
return cram(stripid(repr(x)), self.maxother)
@@ -996,19 +1014,19 @@ class TextDoc(Doc):
def bold(self, text):
"""Format a string in bold by overstriking."""
- return join(map(lambda ch: ch + '\b' + ch, text), '')
+ return ''.join(ch + '\b' + ch for ch in 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')
+ lines = [prefix + line for line in text.split('\n')]
+ if lines: lines[-1] = lines[-1].rstrip()
+ return '\n'.join(lines)
def section(self, title, contents):
"""Format a section with a given heading."""
- return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n'
+ clean_contents = self.indent(contents).rstrip()
+ return self.bold(title) + '\n' + clean_contents + '\n\n'
# ---------------------------------------------- type-specific routines
@@ -1020,8 +1038,8 @@ class TextDoc(Doc):
c, bases = entry
result = result + prefix + classname(c, modname)
if bases and bases != (parent,):
- parents = map(lambda c, m=modname: classname(c, m), bases)
- result = result + '(%s)' % join(parents, ', ')
+ parents = (classname(c, modname) for c in bases)
+ result = result + '(%s)' % ', '.join(parents)
result = result + '\n'
elif type(entry) is type([]):
result = result + self.formattree(
@@ -1033,21 +1051,17 @@ class TextDoc(Doc):
name = object.__name__ # ignore the passed-in name
synop, desc = splitdoc(getdoc(object))
result = self.section('NAME', name + (synop and ' - ' + synop))
-
- try:
- all = object.__all__
- except AttributeError:
- all = None
-
- try:
- file = inspect.getabsfile(object)
- except TypeError:
- file = '(built-in)'
- result = result + self.section('FILE', file)
-
+ all = getattr(object, '__all__', None)
docloc = self.getdocloc(object)
if docloc is not None:
- result = result + self.section('MODULE DOCS', docloc)
+ result = result + self.section('MODULE REFERENCE', docloc + """
+
+The following documentation is automatically generated from the Python
+source files. It may be incomplete, incorrect or include features that
+are considered implementation detail and may vary between Python
+implementations. When in doubt, consult the module reference at the
+location listed above.
+""")
if desc:
result = result + self.section('DESCRIPTION', desc)
@@ -1083,7 +1097,7 @@ class TextDoc(Doc):
modpkgs.sort()
result = result + self.section(
- 'PACKAGE CONTENTS', join(modpkgs, '\n'))
+ 'PACKAGE CONTENTS', '\n'.join(modpkgs))
# Detect submodules as sometimes created by C extensions
submodules = []
@@ -1093,32 +1107,32 @@ class TextDoc(Doc):
if submodules:
submodules.sort()
result = result + self.section(
- 'SUBMODULES', join(submodules, '\n'))
+ 'SUBMODULES', '\n'.join(submodules))
if classes:
- classlist = map(lambda key_value: key_value[1], classes)
+ classlist = [value for key, value in classes]
contents = [self.formattree(
inspect.getclasstree(classlist, 1), name)]
for key, value in classes:
contents.append(self.document(value, key, name))
- result = result + self.section('CLASSES', join(contents, '\n'))
+ result = result + self.section('CLASSES', '\n'.join(contents))
if funcs:
contents = []
for key, value in funcs:
contents.append(self.document(value, key, name))
- result = result + self.section('FUNCTIONS', join(contents, '\n'))
+ result = result + self.section('FUNCTIONS', '\n'.join(contents))
if data:
contents = []
for key, value in data:
contents.append(self.docother(value, key, name, maxlen=70))
- result = result + self.section('DATA', join(contents, '\n'))
+ result = result + self.section('DATA', '\n'.join(contents))
if hasattr(object, '__version__'):
version = str(object.__version__)
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
- version = strip(version[11:-1])
+ version = version[11:-1].strip()
result = result + self.section('VERSION', version)
if hasattr(object, '__date__'):
result = result + self.section('DATE', str(object.__date__))
@@ -1126,6 +1140,11 @@ class TextDoc(Doc):
result = result + self.section('AUTHOR', str(object.__author__))
if hasattr(object, '__credits__'):
result = result + self.section('CREDITS', str(object.__credits__))
+ try:
+ file = inspect.getabsfile(object)
+ except TypeError:
+ file = '(built-in)'
+ result = result + self.section('FILE', file)
return result
def docclass(self, object, name=None, mod=None, *ignored):
@@ -1143,7 +1162,7 @@ class TextDoc(Doc):
title = self.bold(name) + ' = class ' + realname
if bases:
parents = map(makename, bases)
- title = title + '(%s)' % join(parents, ', ')
+ title = title + '(%s)' % ', '.join(parents)
doc = getdoc(object)
contents = doc and [doc + '\n'] or []
@@ -1199,8 +1218,7 @@ class TextDoc(Doc):
hr.maybe()
push(msg)
for name, kind, homecls, value in ok:
- if (hasattr(value, '__call__') or
- inspect.isdatadescriptor(value)):
+ if callable(value) or inspect.isdatadescriptor(value):
doc = getdoc(value)
else:
doc = None
@@ -1208,8 +1226,10 @@ class TextDoc(Doc):
name, mod, maxlen=70, doc=doc) + '\n')
return attrs
- attrs = filter(lambda data: visiblename(data[0], obj=object),
- classify_class_attrs(object))
+ attrs = [(name, kind, cls, value)
+ for name, kind, cls, value in classify_class_attrs(object)
+ if visiblename(name, obj=object)]
+
while attrs:
if mro:
thisclass = mro.popleft()
@@ -1217,7 +1237,7 @@ class TextDoc(Doc):
thisclass = attrs[0][2]
attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
- if thisclass is __builtin__.object:
+ if thisclass is builtins.object:
attrs = inherited
continue
elif thisclass is object:
@@ -1246,7 +1266,7 @@ class TextDoc(Doc):
contents = '\n'.join(contents)
if not contents:
return title + '\n'
- return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
+ return title + '\n' + self.indent(contents.rstrip(), ' | ') + '\n'
def formatvalue(self, object):
"""Format an argument default value as text."""
@@ -1259,17 +1279,17 @@ class TextDoc(Doc):
note = ''
skipdocs = 0
if inspect.ismethod(object):
- imclass = object.im_class
+ imclass = object.__self__.__class__
if cl:
if imclass is not cl:
note = ' from ' + classname(imclass, mod)
else:
- if object.im_self is not None:
+ if object.__self__ is not None:
note = ' method of %s instance' % classname(
- object.im_self.__class__, mod)
+ object.__self__.__class__, mod)
else:
note = ' unbound %s method' % classname(imclass,mod)
- object = object.im_func
+ object = object.__func__
if name == realname:
title = self.bold(realname)
@@ -1279,11 +1299,17 @@ class TextDoc(Doc):
skipdocs = 1
title = self.bold(name) + ' = ' + realname
if inspect.isfunction(object):
- args, varargs, varkw, defaults = inspect.getargspec(object)
+ args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \
+ inspect.getfullargspec(object)
argspec = inspect.formatargspec(
- args, varargs, varkw, defaults, formatvalue=self.formatvalue)
+ args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann,
+ formatvalue=self.formatvalue,
+ formatannotation=inspect.formatannotationrelativeto(object))
if realname == '<lambda>':
title = self.bold(name) + ' lambda '
+ # XXX lambda's won't usually have func_annotations['return']
+ # since the syntax doesn't support but it is possible.
+ # So removing parentheses isn't truly safe.
argspec = argspec[1:-1] # remove parentheses
else:
argspec = '(...)'
@@ -1293,7 +1319,7 @@ class TextDoc(Doc):
return decl + '\n'
else:
doc = getdoc(object) or ''
- return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
+ return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
def _docdescriptor(self, name, value, mod):
results = []
@@ -1328,6 +1354,11 @@ class TextDoc(Doc):
line += '\n' + self.indent(str(doc))
return line
+class _PlainTextDoc(TextDoc):
+ """Subclass of TextDoc which overrides string styling"""
+ def bold(self, text):
+ return text
+
# --------------------------------------------------------- user interfaces
def pager(text):
@@ -1338,7 +1369,7 @@ def pager(text):
def getpager():
"""Decide what method to use for paging through text."""
- if type(sys.stdout) is not types.FileType:
+ if not hasattr(sys.stdout, "isatty"):
return plainpager
if not sys.stdin.isatty() or not sys.stdout.isatty():
return plainpager
@@ -1394,7 +1425,7 @@ def tempfilepager(text, cmd):
def ttypager(text):
"""Page through text on a text terminal."""
- lines = split(plain(text), '\n')
+ lines = plain(text).split('\n')
try:
import tty
fd = sys.stdin.fileno()
@@ -1407,7 +1438,7 @@ def ttypager(text):
try:
r = inc = os.environ.get('LINES', 25) - 1
- sys.stdout.write(join(lines[:inc], '\n') + '\n')
+ sys.stdout.write('\n'.join(lines[:inc]) + '\n')
while lines[r:]:
sys.stdout.write('-- more --')
sys.stdout.flush()
@@ -1423,7 +1454,7 @@ def ttypager(text):
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')
+ sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n')
r = r + inc
finally:
@@ -1459,22 +1490,20 @@ def describe(thing):
return 'function ' + thing.__name__
if inspect.ismethod(thing):
return 'method ' + thing.__name__
- if type(thing) is types.InstanceType:
- return 'instance of ' + thing.__class__.__name__
return type(thing).__name__
def locate(path, forceload=0):
"""Locate an object by name or dotted path, importing as necessary."""
- parts = [part for part in split(path, '.') if part]
+ parts = [part for part in path.split('.') if part]
module, n = None, 0
while n < len(parts):
- nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
+ nextmodule = safeimport('.'.join(parts[:n+1]), forceload)
if nextmodule: module, n = nextmodule, n + 1
else: break
if module:
object = module
else:
- object = __builtin__
+ object = builtins
for part in parts[n:]:
try:
object = getattr(object, part)
@@ -1485,24 +1514,25 @@ def locate(path, forceload=0):
# --------------------------------------- interactive interpreter interface
text = TextDoc()
+plaintext = _PlainTextDoc()
html = HTMLDoc()
-class _OldStyleClass: pass
-_OLD_INSTANCE_TYPE = type(_OldStyleClass())
-
def resolve(thing, forceload=0):
"""Given an object or a path to an object, get the object and its name."""
if isinstance(thing, str):
object = locate(thing, forceload)
if not object:
- raise ImportError, 'no Python documentation found for %r' % thing
+ raise ImportError('no Python documentation found for %r' % thing)
return object, thing
else:
name = getattr(thing, '__name__', None)
return thing, name if isinstance(name, str) else None
-def render_doc(thing, title='Python Library Documentation: %s', forceload=0):
+def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
+ renderer=None):
"""Render text documentation, given an object or a path to an object."""
+ if renderer is None:
+ renderer = text
object, name = resolve(thing, forceload)
desc = describe(object)
module = inspect.getmodule(object)
@@ -1510,11 +1540,8 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0):
desc += ' in ' + name[:name.rfind('.')]
elif module and module is not object:
desc += ' in module ' + module.__name__
- if type(object) is _OLD_INSTANCE_TYPE:
- # If the passed object is an instance of an old-style class,
- # document its available methods instead of its value.
- object = object.__class__
- elif not (inspect.ismodule(object) or
+
+ if not (inspect.ismodule(object) or
inspect.isclass(object) or
inspect.isroutine(object) or
inspect.isgetsetdescriptor(object) or
@@ -1524,26 +1551,30 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0):
# document its available methods instead of its value.
object = type(object)
desc += ' object'
- return title % desc + '\n\n' + text.document(object, name)
+ return title % desc + '\n\n' + renderer.document(object, name)
-def doc(thing, title='Python Library Documentation: %s', forceload=0):
+def doc(thing, title='Python Library Documentation: %s', forceload=0,
+ output=None):
"""Display text documentation, given an object or a path to an object."""
try:
- pager(render_doc(thing, title, forceload))
- except (ImportError, ErrorDuringImport), value:
- print value
+ if output is None:
+ pager(render_doc(thing, title, forceload))
+ else:
+ output.write(render_doc(thing, title, forceload, plaintext))
+ except (ImportError, ErrorDuringImport) as value:
+ print(value)
def writedoc(thing, forceload=0):
"""Write HTML documentation to a file in the current directory."""
try:
object, name = resolve(thing, forceload)
page = html.page(describe(object), html.document(object, name))
- file = open(name + '.html', 'w')
+ file = open(name + '.html', 'w', encoding='utf-8')
file.write(page)
file.close()
- print 'wrote', name + '.html'
- except (ImportError, ErrorDuringImport), value:
- print value
+ print('wrote', name + '.html')
+ except (ImportError, ErrorDuringImport) as value:
+ print(value)
def writedocs(dir, pkgpath='', done=None):
"""Write out HTML documentation for all modules in a directory tree."""
@@ -1566,6 +1597,9 @@ class Helper:
# in Doc/ and copying the output file into the Lib/ directory.
keywords = {
+ 'False': '',
+ 'None': '',
+ 'True': '',
'and': 'BOOLEAN',
'as': 'with',
'assert': ('assert', ''),
@@ -1577,20 +1611,19 @@ class Helper:
'elif': 'if',
'else': ('else', 'while for'),
'except': 'try',
- 'exec': ('exec', ''),
'finally': 'try',
'for': ('for', 'break continue while'),
'from': 'import',
- 'global': ('global', 'NAMESPACES'),
+ 'global': ('global', 'nonlocal NAMESPACES'),
'if': ('if', 'TRUTHVALUE'),
'import': ('import', 'MODULES'),
- 'in': ('in', 'SEQUENCEMETHODS2'),
+ 'in': ('in', 'SEQUENCEMETHODS'),
'is': 'COMPARISON',
'lambda': ('lambda', 'FUNCTIONS'),
+ 'nonlocal': ('nonlocal', 'global NAMESPACES'),
'not': 'BOOLEAN',
'or': 'BOOLEAN',
'pass': ('pass', ''),
- 'print': ('print', ''),
'raise': ('raise', 'EXCEPTIONS'),
'return': ('return', 'FUNCTIONS'),
'try': ('try', 'EXCEPTIONS'),
@@ -1601,7 +1634,7 @@ class Helper:
# Either add symbols to this dictionary or to the symbols dictionary
# directly: Whichever is easier. They are merged later.
_symbols_inverse = {
- 'STRINGS' : ("'", "'''", "r'", "u'", '"""', '"', 'r"', 'u"'),
+ 'STRINGS' : ("'", "'''", "r'", "b'", '"""', '"', 'r"', 'b"'),
'OPERATORS' : ('+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&',
'|', '^', '~', '<', '>', '<=', '>=', '==', '!=', '<>'),
'COMPARISON' : ('<', '>', '<=', '>=', '==', '!=', '<>'),
@@ -1628,7 +1661,7 @@ class Helper:
'[': 'LISTS SUBSCRIPTS SLICINGS',
']': 'LISTS SUBSCRIPTS SLICINGS'
}
- for topic, symbols_ in _symbols_inverse.iteritems():
+ for topic, symbols_ in _symbols_inverse.items():
for symbol in symbols_:
topics = symbols.get(symbol, topic)
if topic not in topics:
@@ -1638,8 +1671,8 @@ class Helper:
topics = {
'TYPES': ('types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS '
'FUNCTIONS CLASSES MODULES FILES inspect'),
- 'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS FORMATTING '
- 'TYPES'),
+ 'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS '
+ 'FORMATTING TYPES'),
'STRINGMETHODS': ('string-methods', 'STRINGS FORMATTING'),
'FORMATTING': ('formatstrings', 'OPERATORS'),
'UNICODE': ('strings', 'encodings unicode SEQUENCES STRINGMETHODS '
@@ -1648,7 +1681,7 @@ class Helper:
'INTEGER': ('integers', 'int range'),
'FLOAT': ('floating', 'float math'),
'COMPLEX': ('imaginary', 'complex cmath'),
- 'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING xrange LISTS'),
+ 'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING range LISTS'),
'MAPPINGS': 'DICTIONARIES',
'FUNCTIONS': ('typesfunctions', 'def TYPES'),
'METHODS': ('typesmethods', 'class def CLASSES TYPES'),
@@ -1666,47 +1699,42 @@ class Helper:
'EXPRESSIONS': ('operator-summary', 'lambda or and not in is BOOLEAN '
'COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER '
'UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES '
- 'LISTS DICTIONARIES BACKQUOTES'),
+ 'LISTS DICTIONARIES'),
'OPERATORS': 'EXPRESSIONS',
'PRECEDENCE': 'EXPRESSIONS',
'OBJECTS': ('objects', 'TYPES'),
'SPECIALMETHODS': ('specialnames', 'BASICMETHODS ATTRIBUTEMETHODS '
- 'CALLABLEMETHODS SEQUENCEMETHODS1 MAPPINGMETHODS '
- 'SEQUENCEMETHODS2 NUMBERMETHODS CLASSES'),
- 'BASICMETHODS': ('customization', 'cmp hash repr str SPECIALMETHODS'),
+ 'CALLABLEMETHODS SEQUENCEMETHODS MAPPINGMETHODS '
+ 'NUMBERMETHODS CLASSES'),
+ 'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'),
'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),
- 'SEQUENCEMETHODS1': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS2 '
- 'SPECIALMETHODS'),
- 'SEQUENCEMETHODS2': ('sequence-methods', 'SEQUENCES SEQUENCEMETHODS1 '
+ 'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS '
'SPECIALMETHODS'),
'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),
'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '
'SPECIALMETHODS'),
'EXECUTION': ('execmodel', 'NAMESPACES DYNAMICFEATURES EXCEPTIONS'),
- 'NAMESPACES': ('naming', 'global ASSIGNMENT DELETION DYNAMICFEATURES'),
+ 'NAMESPACES': ('naming', 'global nonlocal ASSIGNMENT DELETION DYNAMICFEATURES'),
'DYNAMICFEATURES': ('dynamic-features', ''),
'SCOPING': 'NAMESPACES',
'FRAMES': 'NAMESPACES',
'EXCEPTIONS': ('exceptions', 'try except finally raise'),
- 'COERCIONS': ('coercion-rules','CONVERSIONS'),
- 'CONVERSIONS': ('conversions', 'COERCIONS'),
+ 'CONVERSIONS': ('conversions', ''),
'IDENTIFIERS': ('identifiers', 'keywords SPECIALIDENTIFIERS'),
'SPECIALIDENTIFIERS': ('id-classes', ''),
'PRIVATENAMES': ('atom-identifiers', ''),
- 'LITERALS': ('atom-literals', 'STRINGS BACKQUOTES NUMBERS '
- 'TUPLELITERALS LISTLITERALS DICTIONARYLITERALS'),
+ 'LITERALS': ('atom-literals', 'STRINGS NUMBERS TUPLELITERALS '
+ 'LISTLITERALS DICTIONARYLITERALS'),
'TUPLES': 'SEQUENCES',
'TUPLELITERALS': ('exprlists', 'TUPLES LITERALS'),
'LISTS': ('typesseq-mutable', 'LISTLITERALS'),
'LISTLITERALS': ('lists', 'LISTS LITERALS'),
'DICTIONARIES': ('typesmapping', 'DICTIONARYLITERALS'),
'DICTIONARYLITERALS': ('dict', 'DICTIONARIES LITERALS'),
- 'BACKQUOTES': ('string-conversions', 'repr str STRINGS LITERALS'),
- 'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr '
- 'ATTRIBUTEMETHODS'),
- 'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS1'),
- 'SLICINGS': ('slicings', 'SEQUENCEMETHODS2'),
+ 'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'),
+ 'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS'),
+ 'SLICINGS': ('slicings', 'SEQUENCEMETHODS'),
'CALLS': ('calls', 'EXPRESSIONS'),
'POWER': ('power', 'EXPRESSIONS'),
'UNARY': ('unary', 'EXPRESSIONS'),
@@ -1719,7 +1747,6 @@ class Helper:
'ASSIGNMENT': ('assignment', 'AUGMENTEDASSIGNMENT'),
'AUGMENTEDASSIGNMENT': ('augassign', 'NUMBERMETHODS'),
'DELETION': 'del',
- 'PRINTING': 'print',
'RETURNING': 'return',
'IMPORTING': 'import',
'CONDITIONAL': 'if',
@@ -1764,14 +1791,14 @@ has the same effect as typing a particular string at the help> prompt.
if not request: break
except (KeyboardInterrupt, EOFError):
break
- request = strip(replace(request, '"', '', "'", ''))
- if lower(request) in ('q', 'quit'): break
+ request = replace(request, '"', '', "'", '').strip()
+ if request.lower() in ('q', 'quit'): break
self.help(request)
def getline(self, prompt):
- """Read one line, using raw_input when available."""
+ """Read one line, using input() when appropriate."""
if self.input is sys.stdin:
- return raw_input(prompt)
+ return input(prompt)
else:
self.output.write(prompt)
self.output.flush()
@@ -1786,13 +1813,16 @@ has the same effect as typing a particular string at the help> prompt.
elif request == 'topics': self.listtopics()
elif request == 'modules': self.listmodules()
elif request[:8] == 'modules ':
- self.listmodules(split(request)[1])
+ self.listmodules(request.split()[1])
elif request in self.symbols: self.showsymbol(request)
+ elif request in ['True', 'False', 'None']:
+ # special case these keywords since they are objects too
+ doc(eval(request), 'Help on %s:')
elif request in self.keywords: self.showtopic(request)
elif request in self.topics: self.showtopic(request)
- elif request: doc(request, 'Help on %s:')
+ elif request: doc(request, 'Help on %s:', output=self._output)
elif isinstance(request, Helper): self()
- else: doc(request, 'Help on %s:')
+ else: doc(request, 'Help on %s:', output=self._output)
self.output.write('\n')
def intro(self):
@@ -1813,17 +1843,16 @@ such as "spam", type "modules spam".
''' % tuple([sys.version[:3]]*2))
def list(self, items, columns=4, width=80):
- items = items[:]
- items.sort()
- colw = width / columns
- rows = (len(items) + columns - 1) / columns
+ items = list(sorted(items))
+ colw = width // columns
+ rows = (len(items) + columns - 1) // columns
for row in range(rows):
for col in range(columns):
i = col * rows + row
if i < len(items):
self.output.write(items[i])
if col < columns - 1:
- self.output.write(' ' + ' ' * (colw-1 - len(items[i])))
+ self.output.write(' ' + ' ' * (colw - 1 - len(items[i])))
self.output.write('\n')
def listkeywords(self):
@@ -1870,16 +1899,43 @@ module "pydoc_data.topics" could not be found.
except KeyError:
self.output.write('no documentation found for %s\n' % repr(topic))
return
- pager(strip(doc) + '\n')
+ pager(doc.strip() + '\n')
if more_xrefs:
xrefs = (xrefs or '') + ' ' + more_xrefs
if xrefs:
- import StringIO, formatter
- buffer = StringIO.StringIO()
+ import formatter
+ buffer = io.StringIO()
formatter.DumbWriter(buffer).send_flowing_data(
- 'Related help topics: ' + join(split(xrefs), ', ') + '\n')
+ 'Related help topics: ' + ', '.join(xrefs.split()) + '\n')
self.output.write('\n%s\n' % buffer.getvalue())
+ def _gettopic(self, topic, more_xrefs=''):
+ """Return unbuffered tuple of (topic, xrefs).
+
+ If an error occurs here, the exception is caught and displayed by
+ the url handler.
+
+ This function duplicates the showtopic method but returns its
+ result directly so it can be formatted for display in an html page.
+ """
+ try:
+ import pydoc_data.topics
+ except ImportError:
+ return('''
+Sorry, topic and keyword documentation is not available because the
+module "pydoc_data.topics" could not be found.
+''' , '')
+ target = self.topics.get(topic, self.keywords.get(topic))
+ if not target:
+ raise ValueError('could not find topic')
+ if isinstance(target, str):
+ return self._gettopic(target, more_xrefs)
+ label, xrefs = target
+ doc = pydoc_data.topics.topics[label]
+ if more_xrefs:
+ xrefs = (xrefs or '') + ' ' + more_xrefs
+ return doc, xrefs
+
def showsymbol(self, symbol):
target = self.symbols[symbol]
topic, _, xrefs = target.partition(' ')
@@ -1901,7 +1957,7 @@ Please wait a moment while I gather a list of all available modules...
def callback(path, modname, desc, modules=modules):
if modname and modname[-9:] == '.__init__':
modname = modname[:-9] + ' (package)'
- if find(modname, '.') < 0:
+ if modname.find('.') < 0:
modules[modname] = 1
def onerror(modname):
callback(None, modname, None)
@@ -1942,7 +1998,7 @@ class ModuleScanner:
"""An interruptible scanner that searches module synopses."""
def run(self, callback, key=None, completer=None, onerror=None):
- if key: key = lower(key)
+ if key: key = key.lower()
self.quit = False
seen = {}
@@ -1952,31 +2008,55 @@ class ModuleScanner:
if key is None:
callback(None, modname, '')
else:
- desc = split(__import__(modname).__doc__ or '', '\n')[0]
- if find(lower(modname + ' - ' + desc), key) >= 0:
+ name = __import__(modname).__doc__ or ''
+ desc = name.split('\n')[0]
+ name = modname + ' - ' + desc
+ if name.lower().find(key) >= 0:
callback(None, modname, desc)
for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror):
if self.quit:
break
+
+ # XXX Skipping this file is a workaround for a bug
+ # that causes python to crash with a segfault.
+ # http://bugs.python.org/issue9319
+ #
+ # TODO Remove this once the bug is fixed.
+ if modname in {'test.badsyntax_pep3120', 'badsyntax_pep3120'}:
+ continue
+
if key is None:
callback(None, modname, '')
else:
- loader = importer.find_module(modname)
- if hasattr(loader,'get_source'):
- import StringIO
- desc = source_synopsis(
- StringIO.StringIO(loader.get_source(modname))
- ) or ''
- if hasattr(loader,'get_filename'):
+ try:
+ loader = importer.find_module(modname)
+ except SyntaxError:
+ # raised by tests for bad coding cookies or BOM
+ continue
+ if hasattr(loader, 'get_source'):
+ try:
+ source = loader.get_source(modname)
+ except UnicodeDecodeError:
+ if onerror:
+ onerror(modname)
+ continue
+ desc = source_synopsis(io.StringIO(source)) or ''
+ if hasattr(loader, 'get_filename'):
path = loader.get_filename(modname)
else:
path = None
else:
- module = loader.load_module(modname)
+ try:
+ module = loader.load_module(modname)
+ except ImportError:
+ if onerror:
+ onerror(modname)
+ continue
desc = (module.__doc__ or '').splitlines()[0]
path = getattr(module,'__file__',None)
- if find(lower(modname + ' - ' + desc), key) >= 0:
+ name = modname + ' - ' + desc
+ if name.lower().find(key) >= 0:
callback(path, modname, desc)
if completer:
@@ -1987,35 +2067,28 @@ def apropos(key):
def callback(path, modname, desc):
if modname[-9:] == '.__init__':
modname = modname[:-9] + ' (package)'
- print modname, desc and '- ' + desc
+ print(modname, desc and '- ' + desc)
def onerror(modname):
pass
with warnings.catch_warnings():
warnings.filterwarnings('ignore') # ignore problems during import
ModuleScanner().run(callback, key, onerror=onerror)
-# --------------------------------------------------- web browser interface
+# --------------------------------------------------- Web browser interface
def serve(port, callback=None, completer=None):
- import BaseHTTPServer, mimetools, select
-
- # 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):
+ import http.server, email.message, select
+
+ msg = 'the pydoc.serve() function is deprecated'
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
+
+ class DocHandler(http.server.BaseHTTPRequestHandler):
def send_document(self, title, contents):
try:
self.send_response(200)
- self.send_header('Content-Type', 'text/html')
+ self.send_header('Content-Type', 'text/html; charset=UTF-8')
self.end_headers()
- self.wfile.write(html.page(title, contents))
+ self.wfile.write(html.page(title, contents).encode('utf-8'))
except IOError: pass
def do_GET(self):
@@ -2025,7 +2098,7 @@ def serve(port, callback=None, completer=None):
if path and path != '.':
try:
obj = locate(path, forceload=1)
- except ErrorDuringImport, value:
+ except ErrorDuringImport as value:
self.send_document(path, html.escape(str(value)))
return
if obj:
@@ -2039,8 +2112,7 @@ def serve(port, callback=None, completer=None):
'#ffffff', '#7799ee')
def bltinlink(name):
return '<a href="%s.html">%s</a>' % (name, name)
- names = filter(lambda x: x != '__main__',
- sys.builtin_module_names)
+ names = [x for x in sys.builtin_module_names if x != '__main__']
contents = html.multicolumn(names, bltinlink)
indices = ['<p>' + html.bigsection(
'Built-in Modules', '#ffffff', '#ee77aa', contents)]
@@ -2048,14 +2120,14 @@ def serve(port, callback=None, completer=None):
seen = {}
for dir in sys.path:
indices.append(html.index(dir, seen))
- contents = heading + join(indices) + '''<p align=right>
+ contents = heading + ' '.join(indices) + '''<p align=right>
<font color="#909090" face="helvetica, arial"><strong>
pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font>'''
self.send_document('Index of Modules', contents)
def log_message(self, *args): pass
- class DocServer(BaseHTTPServer.HTTPServer):
+ class DocServer(http.server.HTTPServer):
def __init__(self, port, callback):
host = 'localhost'
self.address = (host, port)
@@ -2069,14 +2141,15 @@ pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font>'''
while not self.quit:
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
if rd: self.handle_request()
+ self.server_close()
def server_activate(self):
self.base.server_activate(self)
if self.callback: self.callback(self)
- DocServer.base = BaseHTTPServer.HTTPServer
+ DocServer.base = http.server.HTTPServer
DocServer.handler = DocHandler
- DocHandler.MessageClass = Message
+ DocHandler.MessageClass = email.message.Message
try:
try:
DocServer(port, callback).serve_until_quit()
@@ -2088,27 +2161,32 @@ pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font>'''
# ----------------------------------------------------- graphical interface
def gui():
- """Graphical interface (starts web server and pops up a control window)."""
+ """Graphical interface (starts Web server and pops up a control window)."""
+
+ msg = ('the pydoc.gui() function and "pydoc -g" option are deprecated\n',
+ 'use "pydoc.browse() function and "pydoc -b" option instead.')
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
+
class GUI:
def __init__(self, window, port=7464):
self.window = window
self.server = None
self.scanner = None
- import Tkinter
- self.server_frm = Tkinter.Frame(window)
- self.title_lbl = Tkinter.Label(self.server_frm,
+ import tkinter
+ self.server_frm = tkinter.Frame(window)
+ self.title_lbl = tkinter.Label(self.server_frm,
text='Starting server...\n ')
- self.open_btn = Tkinter.Button(self.server_frm,
+ self.open_btn = tkinter.Button(self.server_frm,
text='open browser', command=self.open, state='disabled')
- self.quit_btn = Tkinter.Button(self.server_frm,
+ self.quit_btn = tkinter.Button(self.server_frm,
text='quit serving', command=self.quit, state='disabled')
- self.search_frm = Tkinter.Frame(window)
- self.search_lbl = Tkinter.Label(self.search_frm, text='Search for')
- self.search_ent = Tkinter.Entry(self.search_frm)
+ self.search_frm = tkinter.Frame(window)
+ self.search_lbl = tkinter.Label(self.search_frm, text='Search for')
+ self.search_ent = tkinter.Entry(self.search_frm)
self.search_ent.bind('<Return>', self.search)
- self.stop_btn = Tkinter.Button(self.search_frm,
+ self.stop_btn = tkinter.Button(self.search_frm,
text='stop', pady=0, command=self.stop, state='disabled')
if sys.platform == 'win32':
# Trying to hide and show this button crashes under Windows.
@@ -2127,17 +2205,17 @@ def gui():
self.search_ent.focus_set()
font = ('helvetica', sys.platform == 'win32' and 8 or 10)
- self.result_lst = Tkinter.Listbox(window, font=font, height=6)
+ self.result_lst = tkinter.Listbox(window, font=font, height=6)
self.result_lst.bind('<Button-1>', self.select)
self.result_lst.bind('<Double-Button-1>', self.goto)
- self.result_scr = Tkinter.Scrollbar(window,
+ self.result_scr = tkinter.Scrollbar(window,
orient='vertical', command=self.result_lst.yview)
self.result_lst.config(yscrollcommand=self.result_scr.set)
- self.result_frm = Tkinter.Frame(window)
- self.goto_btn = Tkinter.Button(self.result_frm,
+ self.result_frm = tkinter.Frame(window)
+ self.goto_btn = tkinter.Button(self.result_frm,
text='go to selected', command=self.goto)
- self.hide_btn = Tkinter.Button(self.result_frm,
+ self.hide_btn = tkinter.Button(self.result_frm,
text='hide results', command=self.hide)
self.goto_btn.pack(side='left', fill='x', expand=1)
self.hide_btn.pack(side='right', fill='x', expand=1)
@@ -2168,15 +2246,8 @@ def gui():
def open(self, event=None, url=None):
url = url or self.server.url
- try:
- import webbrowser
- webbrowser.open(url)
- except ImportError: # pre-webbrowser.py compatibility
- if sys.platform == 'win32':
- os.system('start "%s"' % url)
- else:
- rc = os.system('netscape -remote "openURL(%s)" &' % url)
- if rc: os.system('netscape "%s" &' % url)
+ import webbrowser
+ webbrowser.open(url)
def quit(self, event=None):
if self.server:
@@ -2226,7 +2297,7 @@ def gui():
def goto(self, event=None):
selection = self.result_lst.curselection()
if selection:
- modname = split(self.result_lst.get(selection[0]))[0]
+ modname = self.result_lst.get(selection[0]).split()[0]
self.open(url=self.server.url + modname + '.html')
def collapse(self):
@@ -2253,9 +2324,9 @@ def gui():
self.stop()
self.collapse()
- import Tkinter
+ import tkinter
try:
- root = Tkinter.Tk()
+ root = tkinter.Tk()
# Tk will crash if pythonw.exe has an XP .manifest
# file and the root has is not destroyed explicitly.
# If the problem is ever fixed in Tk, the explicit
@@ -2268,15 +2339,453 @@ def gui():
except KeyboardInterrupt:
pass
+
+# --------------------------------------- enhanced Web browser interface
+
+def _start_server(urlhandler, port):
+ """Start an HTTP server thread on a specific port.
+
+ Start an HTML/text server thread, so HTML or text documents can be
+ browsed dynamically and interactively with a Web browser. Example use:
+
+ >>> import time
+ >>> import pydoc
+
+ Define a URL handler. To determine what the client is asking
+ for, check the URL and content_type.
+
+ Then get or generate some text or HTML code and return it.
+
+ >>> def my_url_handler(url, content_type):
+ ... text = 'the URL sent was: (%s, %s)' % (url, content_type)
+ ... return text
+
+ Start server thread on port 0.
+ If you use port 0, the server will pick a random port number.
+ You can then use serverthread.port to get the port number.
+
+ >>> port = 0
+ >>> serverthread = pydoc._start_server(my_url_handler, port)
+
+ Check that the server is really started. If it is, open browser
+ and get first page. Use serverthread.url as the starting page.
+
+ >>> if serverthread.serving:
+ ... import webbrowser
+
+ The next two lines are commented out so a browser doesn't open if
+ doctest is run on this module.
+
+ #... webbrowser.open(serverthread.url)
+ #True
+
+ Let the server do its thing. We just need to monitor its status.
+ Use time.sleep so the loop doesn't hog the CPU.
+
+ >>> starttime = time.time()
+ >>> timeout = 1 #seconds
+
+ This is a short timeout for testing purposes.
+
+ >>> while serverthread.serving:
+ ... time.sleep(.01)
+ ... if serverthread.serving and time.time() - starttime > timeout:
+ ... serverthread.stop()
+ ... break
+
+ Print any errors that may have occurred.
+
+ >>> print(serverthread.error)
+ None
+ """
+ import http.server
+ import email.message
+ import select
+ import threading
+
+ class DocHandler(http.server.BaseHTTPRequestHandler):
+
+ def do_GET(self):
+ """Process a request from an HTML browser.
+
+ The URL received is in self.path.
+ Get an HTML page from self.urlhandler and send it.
+ """
+ if self.path.endswith('.css'):
+ content_type = 'text/css'
+ else:
+ content_type = 'text/html'
+ self.send_response(200)
+ self.send_header('Content-Type', '%s; charset=UTF-8' % content_type)
+ self.end_headers()
+ self.wfile.write(self.urlhandler(
+ self.path, content_type).encode('utf-8'))
+
+ def log_message(self, *args):
+ # Don't log messages.
+ pass
+
+ class DocServer(http.server.HTTPServer):
+
+ def __init__(self, port, callback):
+ self.host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
+ self.address = ('', port)
+ self.callback = callback
+ self.base.__init__(self, self.address, self.handler)
+ self.quit = False
+
+ def serve_until_quit(self):
+ while not self.quit:
+ rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
+ if rd:
+ self.handle_request()
+ self.server_close()
+
+ def server_activate(self):
+ self.base.server_activate(self)
+ if self.callback:
+ self.callback(self)
+
+ class ServerThread(threading.Thread):
+
+ def __init__(self, urlhandler, port):
+ self.urlhandler = urlhandler
+ self.port = int(port)
+ threading.Thread.__init__(self)
+ self.serving = False
+ self.error = None
+
+ def run(self):
+ """Start the server."""
+ try:
+ DocServer.base = http.server.HTTPServer
+ DocServer.handler = DocHandler
+ DocHandler.MessageClass = email.message.Message
+ DocHandler.urlhandler = staticmethod(self.urlhandler)
+ docsvr = DocServer(self.port, self.ready)
+ self.docserver = docsvr
+ docsvr.serve_until_quit()
+ except Exception as e:
+ self.error = e
+
+ def ready(self, server):
+ self.serving = True
+ self.host = server.host
+ self.port = server.server_port
+ self.url = 'http://%s:%d/' % (self.host, self.port)
+
+ def stop(self):
+ """Stop the server and this thread nicely"""
+ self.docserver.quit = True
+ self.serving = False
+ self.url = None
+
+ thread = ServerThread(urlhandler, port)
+ thread.start()
+ # Wait until thread.serving is True to make sure we are
+ # really up before returning.
+ while not thread.error and not thread.serving:
+ time.sleep(.01)
+ return thread
+
+
+def _url_handler(url, content_type="text/html"):
+ """The pydoc url handler for use with the pydoc server.
+
+ If the content_type is 'text/css', the _pydoc.css style
+ sheet is read and returned if it exits.
+
+ If the content_type is 'text/html', then the result of
+ get_html_page(url) is returned.
+ """
+ class _HTMLDoc(HTMLDoc):
+
+ def page(self, title, contents):
+ """Format an HTML page."""
+ css_path = "pydoc_data/_pydoc.css"
+ css_link = (
+ '<link rel="stylesheet" type="text/css" href="%s">' %
+ css_path)
+ return '''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html><head><title>Pydoc: %s</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+%s</head><body bgcolor="#f0f0f8">%s<div style="clear:both;padding-top:.5em;">%s</div>
+</body></html>''' % (title, css_link, html_navbar(), contents)
+
+ def filelink(self, url, path):
+ return '<a href="getfile?key=%s">%s</a>' % (url, path)
+
+
+ html = _HTMLDoc()
+
+ def html_navbar():
+ version = html.escape("%s [%s, %s]" % (platform.python_version(),
+ platform.python_build()[0],
+ platform.python_compiler()))
+ return """
+ <div style='float:left'>
+ Python %s<br>%s
+ </div>
+ <div style='float:right'>
+ <div style='text-align:center'>
+ <a href="index.html">Module Index</a>
+ : <a href="topics.html">Topics</a>
+ : <a href="keywords.html">Keywords</a>
+ </div>
+ <div>
+ <form action="get" style='display:inline;'>
+ <input type=text name=key size=15>
+ <input type=submit value="Get">
+ </form>&nbsp;
+ <form action="search" style='display:inline;'>
+ <input type=text name=key size=15>
+ <input type=submit value="Search">
+ </form>
+ </div>
+ </div>
+ """ % (version, html.escape(platform.platform(terse=True)))
+
+ def html_index():
+ """Module Index page."""
+
+ def bltinlink(name):
+ return '<a href="%s.html">%s</a>' % (name, name)
+
+ heading = html.heading(
+ '<big><big><strong>Index of Modules</strong></big></big>',
+ '#ffffff', '#7799ee')
+ names = [name for name in sys.builtin_module_names
+ if name != '__main__']
+ contents = html.multicolumn(names, bltinlink)
+ contents = [heading, '<p>' + html.bigsection(
+ 'Built-in Modules', '#ffffff', '#ee77aa', contents)]
+
+ seen = {}
+ for dir in sys.path:
+ contents.append(html.index(dir, seen))
+
+ contents.append(
+ '<p align=right><font color="#909090" face="helvetica,'
+ 'arial"><strong>pydoc</strong> by Ka-Ping Yee'
+ '&lt;ping@lfw.org&gt;</font>')
+ return 'Index of Modules', ''.join(contents)
+
+ def html_search(key):
+ """Search results page."""
+ # scan for modules
+ search_result = []
+
+ def callback(path, modname, desc):
+ if modname[-9:] == '.__init__':
+ modname = modname[:-9] + ' (package)'
+ search_result.append((modname, desc and '- ' + desc))
+
+ with warnings.catch_warnings():
+ warnings.filterwarnings('ignore') # ignore problems during import
+ ModuleScanner().run(callback, key)
+
+ # format page
+ def bltinlink(name):
+ return '<a href="%s.html">%s</a>' % (name, name)
+
+ results = []
+ heading = html.heading(
+ '<big><big><strong>Search Results</strong></big></big>',
+ '#ffffff', '#7799ee')
+ for name, desc in search_result:
+ results.append(bltinlink(name) + desc)
+ contents = heading + html.bigsection(
+ 'key = %s' % key, '#ffffff', '#ee77aa', '<br>'.join(results))
+ return 'Search Results', contents
+
+ def html_getfile(path):
+ """Get and display a source file listing safely."""
+ path = path.replace('%20', ' ')
+ with tokenize.open(path) as fp:
+ lines = html.escape(fp.read())
+ body = '<pre>%s</pre>' % lines
+ heading = html.heading(
+ '<big><big><strong>File Listing</strong></big></big>',
+ '#ffffff', '#7799ee')
+ contents = heading + html.bigsection(
+ 'File: %s' % path, '#ffffff', '#ee77aa', body)
+ return 'getfile %s' % path, contents
+
+ def html_topics():
+ """Index of topic texts available."""
+
+ def bltinlink(name):
+ return '<a href="topic?key=%s">%s</a>' % (name, name)
+
+ heading = html.heading(
+ '<big><big><strong>INDEX</strong></big></big>',
+ '#ffffff', '#7799ee')
+ names = sorted(Helper.topics.keys())
+
+ contents = html.multicolumn(names, bltinlink)
+ contents = heading + html.bigsection(
+ 'Topics', '#ffffff', '#ee77aa', contents)
+ return 'Topics', contents
+
+ def html_keywords():
+ """Index of keywords."""
+ heading = html.heading(
+ '<big><big><strong>INDEX</strong></big></big>',
+ '#ffffff', '#7799ee')
+ names = sorted(Helper.keywords.keys())
+
+ def bltinlink(name):
+ return '<a href="topic?key=%s">%s</a>' % (name, name)
+
+ contents = html.multicolumn(names, bltinlink)
+ contents = heading + html.bigsection(
+ 'Keywords', '#ffffff', '#ee77aa', contents)
+ return 'Keywords', contents
+
+ def html_topicpage(topic):
+ """Topic or keyword help page."""
+ buf = io.StringIO()
+ htmlhelp = Helper(buf, buf)
+ contents, xrefs = htmlhelp._gettopic(topic)
+ if topic in htmlhelp.keywords:
+ title = 'KEYWORD'
+ else:
+ title = 'TOPIC'
+ heading = html.heading(
+ '<big><big><strong>%s</strong></big></big>' % title,
+ '#ffffff', '#7799ee')
+ contents = '<pre>%s</pre>' % html.markup(contents)
+ contents = html.bigsection(topic , '#ffffff','#ee77aa', contents)
+ if xrefs:
+ xrefs = sorted(xrefs.split())
+
+ def bltinlink(name):
+ return '<a href="topic?key=%s">%s</a>' % (name, name)
+
+ xrefs = html.multicolumn(xrefs, bltinlink)
+ xrefs = html.section('Related help topics: ',
+ '#ffffff', '#ee77aa', xrefs)
+ return ('%s %s' % (title, topic),
+ ''.join((heading, contents, xrefs)))
+
+ def html_getobj(url):
+ obj = locate(url, forceload=1)
+ if obj is None and url != 'None':
+ raise ValueError('could not find object')
+ title = describe(obj)
+ content = html.document(obj, url)
+ return title, content
+
+ def html_error(url, exc):
+ heading = html.heading(
+ '<big><big><strong>Error</strong></big></big>',
+ '#ffffff', '#7799ee')
+ contents = '<br>'.join(html.escape(line) for line in
+ format_exception_only(type(exc), exc))
+ contents = heading + html.bigsection(url, '#ffffff', '#bb0000',
+ contents)
+ return "Error - %s" % url, contents
+
+ def get_html_page(url):
+ """Generate an HTML page for url."""
+ complete_url = url
+ if url.endswith('.html'):
+ url = url[:-5]
+ try:
+ if url in ("", "index"):
+ title, content = html_index()
+ elif url == "topics":
+ title, content = html_topics()
+ elif url == "keywords":
+ title, content = html_keywords()
+ elif '=' in url:
+ op, _, url = url.partition('=')
+ if op == "search?key":
+ title, content = html_search(url)
+ elif op == "getfile?key":
+ title, content = html_getfile(url)
+ elif op == "topic?key":
+ # try topics first, then objects.
+ try:
+ title, content = html_topicpage(url)
+ except ValueError:
+ title, content = html_getobj(url)
+ elif op == "get?key":
+ # try objects first, then topics.
+ if url in ("", "index"):
+ title, content = html_index()
+ else:
+ try:
+ title, content = html_getobj(url)
+ except ValueError:
+ title, content = html_topicpage(url)
+ else:
+ raise ValueError('bad pydoc url')
+ else:
+ title, content = html_getobj(url)
+ except Exception as exc:
+ # Catch any errors and display them in an error page.
+ title, content = html_error(complete_url, exc)
+ return html.page(title, content)
+
+ if url.startswith('/'):
+ url = url[1:]
+ if content_type == 'text/css':
+ path_here = os.path.dirname(os.path.realpath(__file__))
+ css_path = os.path.join(path_here, url)
+ with open(css_path) as fp:
+ return ''.join(fp.readlines())
+ elif content_type == 'text/html':
+ return get_html_page(url)
+ # Errors outside the url handler are caught by the server.
+ raise TypeError('unknown content type %r for url %s' % (content_type, url))
+
+
+def browse(port=0, *, open_browser=True):
+ """Start the enhanced pydoc Web server and open a Web browser.
+
+ Use port '0' to start the server on an arbitrary port.
+ Set open_browser to False to suppress opening a browser.
+ """
+ import webbrowser
+ serverthread = _start_server(_url_handler, port)
+ if serverthread.error:
+ print(serverthread.error)
+ return
+ if serverthread.serving:
+ server_help_msg = 'Server commands: [b]rowser, [q]uit'
+ if open_browser:
+ webbrowser.open(serverthread.url)
+ try:
+ print('Server ready at', serverthread.url)
+ print(server_help_msg)
+ while serverthread.serving:
+ cmd = input('server> ')
+ cmd = cmd.lower()
+ if cmd == 'q':
+ break
+ elif cmd == 'b':
+ webbrowser.open(serverthread.url)
+ else:
+ print(server_help_msg)
+ except (KeyboardInterrupt, EOFError):
+ print()
+ finally:
+ if serverthread.serving:
+ serverthread.stop()
+ print('Server stopped')
+
+
# -------------------------------------------------- command-line interface
def ispath(x):
- return isinstance(x, str) and find(x, os.sep) >= 0
+ return isinstance(x, str) and x.find(os.sep) >= 0
def cli():
"""Command-line interface (looks at sys.argv to decide what to do)."""
import getopt
- class BadUsage: pass
+ class BadUsage(Exception): pass
# Scripts don't get the current directory in their path by default
# unless they are run with the '-m' switch
@@ -2287,34 +2796,37 @@ def cli():
sys.path.insert(0, '.')
try:
- opts, args = getopt.getopt(sys.argv[1:], 'gk:p:w')
- writing = 0
-
+ opts, args = getopt.getopt(sys.argv[1:], 'bgk:p:w')
+ writing = False
+ start_server = False
+ open_browser = False
+ port = None
for opt, val in opts:
if opt == '-g':
gui()
return
+ if opt == '-b':
+ start_server = True
+ open_browser = True
if opt == '-k':
apropos(val)
return
if opt == '-p':
- try:
- port = int(val)
- except ValueError:
- raise BadUsage
- def ready(server):
- print 'pydoc server ready at %s' % server.url
- def stopped():
- print 'pydoc server stopped'
- serve(port, ready, stopped)
- return
+ start_server = True
+ port = val
if opt == '-w':
- writing = 1
+ writing = True
+
+ if start_server == True:
+ if port == None:
+ port = 0
+ browse(port, open_browser=open_browser)
+ return
if not args: raise BadUsage
for arg in args:
if ispath(arg) and not os.path.exists(arg):
- print 'file %r does not exist' % arg
+ print('file %r does not exist' % arg)
break
try:
if ispath(arg) and os.path.isfile(arg):
@@ -2326,34 +2838,41 @@ def cli():
writedoc(arg)
else:
help.help(arg)
- except ErrorDuringImport, value:
- print value
+ except ErrorDuringImport as value:
+ print(value)
except (getopt.error, BadUsage):
- cmd = os.path.basename(sys.argv[0])
- print """pydoc - the Python documentation tool
+ cmd = os.path.splitext(os.path.basename(sys.argv[0]))[0]
+ print("""pydoc - the Python documentation tool
-%s <name> ...
+{cmd} <name> ...
Show text documentation on something. <name> may be the name of a
Python keyword, topic, function, module, or package, or a dotted
reference to a class or function within a module or module in a
- package. If <name> contains a '%s', it is used as the path to a
+ package. If <name> contains a '{sep}', it is used as the path to a
Python source file to document. If name is 'keywords', 'topics',
or 'modules', a listing of these things is displayed.
-%s -k <keyword>
+{cmd} -k <keyword>
Search for a keyword in the synopsis lines of all available modules.
-%s -p <port>
- Start an HTTP server on the given port on the local machine.
+{cmd} -p <port>
+ Start an HTTP server on the given port on the local machine. Port
+ number 0 can be used to get an arbitrary unused port.
+
+{cmd} -b
+ Start an HTTP server on an arbitrary unused port and open a Web browser
+ to interactively browse documentation. The -p option can be used with
+ the -b option to explicitly specify the server port.
-%s -g
- Pop up a graphical interface for finding and serving documentation.
+{cmd} -g
+ Deprecated.
-%s -w <name> ...
+{cmd} -w <name> ...
Write out the HTML documentation for a module to a file in the current
- directory. If <name> contains a '%s', it is treated as a filename; if
+ directory. If <name> contains a '{sep}', it is treated as a filename; if
it names a directory, documentation is written for all the contents.
-""" % (cmd, os.sep, cmd, cmd, cmd, cmd, os.sep)
+""".format(cmd=cmd, sep=os.sep))
-if __name__ == '__main__': cli()
+if __name__ == '__main__':
+ cli()