'''.format(title, extras or ' ')
def html_section(
self, title, contents, width=6,
prelude='', marginalia=None, gap=' ',
css_class=''):
"""Format a section with a heading."""
result = '''
{}
'''.format(css_class, title)
if prelude:
result = result + '''
{}
{}
{}
'''.format(marginalia, prelude, gap)
elif marginalia:
result = result + '''
{}
{}
'''.format(marginalia, gap)
contents = '{}
'.format(contents)
return result + '\n
' + contents
def bigsection(self, title, *args, **kwargs):
"""Format a section with a big heading."""
title = '{}'.format(title)
return self.html_section(title, *args, **kwargs)
def preformat(self, text):
"""Format literal preformatted text."""
text = self.escape(text.expandtabs())
return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
' ', ' ', '\n', ' \n')
def multicolumn(self, list, format, cols=4):
"""Format a list of items into a multi-column list."""
result = ''
rows = (len(list)+cols-1)//cols
for col in range(cols):
result = (
result + '
'
% (100//cols))
for i in range(rows*col, rows*col+rows):
if i < len(list):
result = result + format(list[i]) + ' \n'
result = result + '
'
return '
%s
' % result
def grey(self, text):
"""Grey span."""
return '%s' % text
def namelink(self, name, *dicts):
"""Make a link for an identifier, given name-to-URL mappings."""
for dict in dicts:
if name in dict:
return '%s' % (dict[name], name)
return name
def classlink(self, object, modname):
"""Make a link for a class."""
name, module = object.__name__, sys.modules.get(object.__module__)
if hasattr(module, name) and getattr(module, name) is object:
return '%s' % (
module.__name__, name, classname(object, modname))
return classname(object, modname)
def modulelink(self, object):
"""Make a link for a module."""
return '%s' % (
object.__name__, object.__name__)
def modpkglink(self, modpkginfo):
"""Make a link for a module or package to display in an index."""
name, path, ispackage, shadowed = modpkginfo
if shadowed:
return self.grey(name)
if path:
url = '%s.%s.html' % (path, name)
else:
url = '%s.html' % name
if ispackage:
text = '%s (package)' % name
else:
text = name
return '%s' % (url, text)
def filelink(self, url, path):
"""Make a link to source file."""
return '%s' % (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.
"""
escape = escape or self.escape
results = []
here = 0
pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
r'RFC[- ]?(\d+)|'
r'PEP[- ]?(\d+)|'
r'(self\.)?(\w+))')
while True:
match = pattern.search(text, here)
if not match:
break
start, end = match.span()
results.append(escape(text[here:start]))
all, scheme, rfc, pep, selfdot, name = match.groups()
if scheme:
url = escape(all).replace('"', '"')
results.append('%s' % (url, url))
elif rfc:
url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
results.append('%s' % (url, escape(all)))
elif pep:
url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
results.append('%s' % (url, escape(all)))
elif text[end:end+1] == '(':
results.append(
self.namelink(name, methods, funcs, classes))
elif selfdot:
results.append('self.%s' % name)
else:
results.append(self.namelink(name, classes))
here = end
results.append(escape(text[here:]))
return ''.join(results)
# --------------------------------------------- type-specific routines
def formattree(self, tree, modname, parent=None):
"""
Produce HTML for a class tree as given by inspect.getclasstree().
"""
result = ''
for entry in tree:
if type(entry) is type(()):
c, bases = entry
result = result + '
'
result = result + self.classlink(c, modname)
if bases and bases != (parent,):
parents = []
for base in bases:
parents.append(self.classlink(base, modname))
result = result + '(' + ', '.join(parents) + ')'
result = result + '\n
'
elif type(entry) is type([]):
result = result + '
\n%s
\n' % self.formattree(
entry, modname, c)
return '
\n%s
\n' % result
def docmodule(self, object, name=None, mod=None, *ignored):
"""Produce HTML documentation for a module object."""
name = object.__name__ # ignore the passed-in name
try:
all = object.__all__
except AttributeError:
all = None
parts = name.split('.')
links = []
for i in range(len(parts)-1):
links.append(
'{}'.format(
'.'.join(parts[:i+1]), parts[i]))
head = '.'.join(links + parts[-1:])
try:
path = inspect.getabsfile(object)
url = path
if sys.platform == 'win32':
import nturl2path
url = nturl2path.pathname2url(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 = 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)
docloc = self.getdocloc(object)
if docloc is not None:
docloc = (
' Module Reference' % locals())
else:
docloc = ''
extras = 'index ' + filelink + docloc
result = self.heading(head, extras)
modules = inspect.getmembers(object, inspect.ismodule)
classes, cdict = [], {}
for key, value in inspect.getmembers(object, inspect.isclass):
# if __all__ exists, believe it. Otherwise use old heuristic.
if (all is not None or
(inspect.getmodule(value) or object) is object):
if visiblename(key, all, object):
classes.append((key, value))
cdict[key] = cdict[value] = '#' + key
for key, value in classes:
for base in value.__bases__:
key, modname = base.__name__, base.__module__
module = sys.modules.get(modname)
if modname != name and module and hasattr(module, key):
if getattr(module, key) is base:
if key not in cdict:
cdict[key] = cdict[base] = (
modname + '.html#' + key)
funcs, fdict = [], {}
for key, value in inspect.getmembers(object, inspect.isroutine):
# if __all__ exists, believe it. Otherwise use old heuristic.
if (all is not None or
inspect.isbuiltin(value) or
inspect.getmodule(value) is object):
if visiblename(key, all, object):
funcs.append((key, value))
fdict[key] = '#-' + key
if inspect.isfunction(value):
fdict[value] = fdict[key]
data = []
for key, value in inspect.getmembers(object, isdata):
if visiblename(key, all, object):
data.append((key, value))
doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
doc = doc and '{}'.format(doc)
result = result + '
%s
\n' % doc
if hasattr(object, '__path__'):
modpkgs = []
for importer, modname, ispkg in pkgutil.iter_modules(
object.__path__):
modpkgs.append((modname, name, ispkg, 0))
modpkgs.sort()
contents = self.multicolumn(modpkgs, self.modpkglink)
result = result + self.bigsection(
'Package Contents', contents, css_class="package")
elif modules:
contents = self.multicolumn(
modules, lambda t: self.modulelink(t[1]))
result = result + self.bigsection(
'Modules', contents, css_class="module")
if 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', ' '.join(contents), css_class="classes")
if funcs:
contents = []
for key, value in funcs:
contents.append(
self.document(value, key, name, fdict, cdict))
result = result + self.bigsection(
'Functions', ' '.join(contents), css_class="functions")
if data:
contents = []
for key, value in data:
contents.append(self.document(value, key))
result = result + self.bigsection(
'Data', ' \n'.join(contents), css_class="data")
if hasattr(object, '__author__'):
contents = self.markup(str(object.__author__), self.preformat)
result = result + self.bigsection(
'Author', contents, css_class="author")
if hasattr(object, '__credits__'):
contents = self.markup(str(object.__credits__), self.preformat)
result = result + self.bigsection(
'Credits', contents, css_class="credits")
return result
def docclass(self, object, name=None, mod=None, funcs={}, classes={},
*ignored):
"""Produce HTML documentation for a class object."""
realname = object.__name__
name = name or realname
bases = object.__bases__
contents = []
push = contents.append
# Cute little class to pump out a horizontal rule between sections.
class HorizontalRule:
def __init__(self):
self.needone = 0
def maybe(self):
if self.needone:
push('\n')
self.needone = 1
hr = HorizontalRule()
# List the mro, if non-trivial.
mro = deque(inspect.getmro(object))
if len(mro) > 2:
hr.maybe()
push('
\n' % name)
if value.__doc__ is not None:
doc = self.markup(getdoc(value), self.preformat)
push('
%s
\n' % doc)
push('
\n')
return ''.join(results)
def docproperty(self, object, name=None, mod=None, cl=None):
"""Produce html documentation for a property."""
return self._docdescriptor(name, object, mod)
def docother(self, object, name=None, mod=None, *ignored):
"""Produce HTML documentation for a data object."""
lhs = name and '%s = ' % name or ''
return lhs + self.repr(object)
def docdata(self, object, name=None, mod=None, cl=None):
"""Produce html documentation for a data descriptor."""
return self._docdescriptor(name, object, mod)
def index(self, dir, shadowed=None):
"""Generate an HTML index for a directory of modules."""
modpkgs = []
if shadowed is None:
shadowed = {}
for importer, name, ispkg in pkgutil.iter_modules([dir]):
if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name):
# ignore a module if its name contains a
# surrogate character
continue
modpkgs.append((name, '', ispkg, name in shadowed))
shadowed[name] = 1
modpkgs.sort()
if len(modpkgs):
contents = self.multicolumn(modpkgs, self.modpkglink)
return self.bigsection(dir, contents, css_class="index")
else:
return ''
def _url_handler(url, content_type="text/html"):
"""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.
See https://github.com/python/cpython/blob/master/Lib/pydoc.py
"""
class _HTMLDoc(CustomHTMLDoc):
def page(self, title, contents):
"""Format an HTML page."""
rich_text_font = get_font(option="rich_font").family()
plain_text_font = get_font(option="font").family()
if is_dark_interface():
css_path = "static/css/dark_pydoc.css"
else:
css_path = "static/css/light_pydoc.css"
css_link = (
'' %
css_path)
code_style = (
'' % plain_text_font)
html_page = '''\
Pydoc: %s
%s%s
%s
""" % (version, html.escape(platform.platform(terse=True)))
def html_index():
"""Index page."""
def bltinlink(name):
return '%s' % (name, name)
heading = html.heading('Index of Modules')
names = [name for name in sys.builtin_module_names
if name != '__main__']
contents = html.multicolumn(names, bltinlink)
contents = [heading, '
' + html.bigsection(
'Built-in Modules', contents, css_class="builtin_modules")]
seen = {}
for dir in sys.path:
contents.append(html.index(dir, seen))
contents.append(
'
pydoc by Ka-Ping Yee'
'<ping@lfw.org>
')
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 '%s' % (name, name)
results = []
heading = html.heading('Search Results')
for name, desc in search_result:
results.append(bltinlink(name) + desc)
contents = heading + html.bigsection(
'key = {}'.format(key), ' '.join(results), css_class="search")
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 = '
' % html.markup(contents)
contents = html.bigsection(topic, contents, css_class="topics")
if xrefs:
xrefs = sorted(xrefs.split())
def bltinlink(name):
return '%s' % (name, name)
xrefs = html.multicolumn(xrefs, bltinlink)
xrefs = html.html_section('Related help topics: ', xrefs,
css_class="topics")
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(
_('There was an error while retrieving documentation '
'for the object you requested: Object could not be found'))
title = describe(obj)
content = html.document(obj, url)
return title, content
def html_error(url, exc):
heading = html.heading('Error')
if DEV:
contents = ' '.join(html.escape(line) for line in
format_exception_only(type(exc), exc))
else:
contents = '%s' % to_text_string(exc)
contents = heading + html.bigsection(url, contents, css_class="error")
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(
_('There was an error while retrieving documentation '
'for the object you requested: Bad URL %s') % 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(
_('There was an error while retrieving documentation '
'for the object you requested: unknown content type %r for url %s')
% (content_type, url))
def _start_server(urlhandler, hostname, port):
"""
Start an HTTP server thread on a specific port.
This is a reimplementation of `pydoc._start_server` to handle connection
errors for 'do_GET'.
Taken from PyDoc: https://github.com/python/cpython/blob/3.7/Lib/pydoc.py
"""
import http.server
import email.message
import select
import threading
import time
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()
try:
self.wfile.write(self.urlhandler(
self.path, content_type).encode('utf-8'))
except ConnectionAbortedError:
# Needed to handle error when client closes the connection,
# for example when the client stops the load of the previously
# requested page. See spyder-ide/spyder#10755
pass
except BrokenPipeError:
# Needed to handle permission error when trying to open a port
# for the web server of the online help.
# See spyder-ide/spyder#13388
pass
def log_message(self, *args):
# Don't log messages.
pass
class DocServer(http.server.HTTPServer):
def __init__(self, host, port, callback):
self.host = host
self.address = (self.host, 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, host, port):
self.urlhandler = urlhandler
self.host = host
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.host, 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.join()
# explicitly break a reference cycle: DocServer.callback
# has indirectly a reference to ServerThread.
self.docserver = None
self.serving = False
self.url = None
thread = ServerThread(urlhandler, hostname, 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