Skip to content

Commit b89cd6b

Browse files
bpo-18387: Add 'symbols' link to pydoc's html menu bar
1 parent 137be34 commit b89cd6b

3 files changed

Lines changed: 56 additions & 52 deletions

File tree

Lib/pydoc.py

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class or function within a module or module in a package. If the
7171
import urllib.parse
7272
import warnings
7373
from collections import deque
74+
from html import escape as html_escape
7475
from reprlib import Repr
7576
from traceback import format_exception_only
7677

@@ -430,9 +431,6 @@ def __init__(self):
430431
self.maxdict = 10
431432
self.maxstring = self.maxother = 100
432433

433-
def escape(self, text):
434-
return replace(text, '&', '&amp;', '<', '&lt;', '>', '&gt;')
435-
436434
def repr(self, object):
437435
return Repr.repr(self, object)
438436

@@ -441,26 +439,26 @@ def repr1(self, x, level):
441439
methodname = 'repr_' + '_'.join(type(x).__name__.split())
442440
if hasattr(self, methodname):
443441
return getattr(self, methodname)(x, level)
444-
return self.escape(cram(stripid(repr(x)), self.maxother))
442+
return html_escape(cram(stripid(repr(x)), self.maxother))
445443

446444
def repr_string(self, x, level):
447445
test = cram(x, self.maxstring)
448446
testrepr = repr(test)
449447
if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
450448
# Backslashes are only literal in the string and are never
451449
# needed to make any special characters, so show a raw string.
452-
return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
450+
return 'r' + testrepr[0] + html_escape(test) + testrepr[0]
453451
return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
454452
r'<font color="#c040c0">\1</font>',
455-
self.escape(testrepr))
453+
html_escape(testrepr, quote=False))
456454

457455
repr_str = repr_string
458456

459457
def repr_instance(self, x, level):
460458
try:
461-
return self.escape(cram(stripid(repr(x)), self.maxstring))
459+
return html_escape(cram(stripid(repr(x)), self.maxstring))
462460
except:
463-
return self.escape('<%s instance>' % x.__class__.__name__)
461+
return html_escape('<%s instance>' % x.__class__.__name__)
464462

465463
repr_unicode = repr_string
466464

@@ -471,7 +469,6 @@ class HTMLDoc(Doc):
471469

472470
_repr_instance = HTMLRepr()
473471
repr = _repr_instance.repr
474-
escape = _repr_instance.escape
475472

476473
def page(self, title, contents):
477474
"""Format an HTML page."""
@@ -523,7 +520,7 @@ def bigsection(self, title, *args):
523520

524521
def preformat(self, text):
525522
"""Format literal preformatted text."""
526-
text = self.escape(text.expandtabs())
523+
text = html_escape(text.expandtabs(), quote=False)
527524
return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
528525
' ', '&nbsp;', '\n', '<br>\n')
529526

@@ -582,7 +579,7 @@ def filelink(self, url, path):
582579
def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
583580
"""Mark up some plain text, given a context of symbols to look for.
584581
Each context dictionary maps object names to anchor names."""
585-
escape = escape or self.escape
582+
escape = escape or html_escape
586583
results = []
587584
here = 0
588585
pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
@@ -667,9 +664,9 @@ def docmodule(self, object, name=None, mod=None, *ignored):
667664
version = str(object.__version__)
668665
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
669666
version = version[11:-1].strip()
670-
info.append('version %s' % self.escape(version))
667+
info.append('version %s' % html_escape(version))
671668
if hasattr(object, '__date__'):
672-
info.append(self.escape(str(object.__date__)))
669+
info.append(html_escape(str(object.__date__)))
673670
if info:
674671
head = head + ' (%s)' % ', '.join(info)
675672
docloc = self.getdocloc(object)
@@ -2072,6 +2069,11 @@ def showsymbol(self, symbol):
20722069
topic, _, xrefs = target.partition(' ')
20732070
self.showtopic(topic, xrefs)
20742071

2072+
def _getsymbol(self, symbol):
2073+
target = self.symbols[symbol]
2074+
topic, _, xrefs = target.partition(' ')
2075+
return self._gettopic(topic, xrefs)
2076+
20752077
def listmodules(self, key=''):
20762078
if key:
20772079
self.output.write('''
@@ -2237,6 +2239,7 @@ def _start_server(urlhandler, hostname, port):
22372239
import email.message
22382240
import select
22392241
import threading
2242+
from urllib.parse import unquote
22402243

22412244
class DocHandler(http.server.BaseHTTPRequestHandler):
22422245

@@ -2354,13 +2357,14 @@ def page(self, title, contents):
23542357
</body></html>''' % (title, css_link, html_navbar(), contents)
23552358

23562359
def filelink(self, url, path):
2357-
return '<a href="getfile?key=%s">%s</a>' % (url, path)
2360+
return ('<a href="getfile?key=%s">%s</a>' %
2361+
(html_escape(url), html_escape(path)))
23582362

23592363

23602364
html = _HTMLDoc()
23612365

23622366
def html_navbar():
2363-
version = html.escape("%s [%s, %s]" % (platform.python_version(),
2367+
version = html_escape("%s [%s, %s]" % (platform.python_version(),
23642368
platform.python_build()[0],
23652369
platform.python_compiler()))
23662370
return """
@@ -2372,6 +2376,7 @@ def html_navbar():
23722376
<a href="index.html">Module Index</a>
23732377
: <a href="topics.html">Topics</a>
23742378
: <a href="keywords.html">Keywords</a>
2379+
: <a href="symbols.html">Symbols</a>
23752380
</div>
23762381
<div>
23772382
<form action="get" style='display:inline;'>
@@ -2384,7 +2389,7 @@ def html_navbar():
23842389
</form>
23852390
</div>
23862391
</div>
2387-
""" % (version, html.escape(platform.platform(terse=True)))
2392+
""" % (version, html_escape(platform.platform(terse=True)))
23882393

23892394
def html_index():
23902395
"""Module Index page."""
@@ -2445,7 +2450,7 @@ def html_getfile(path):
24452450
"""Get and display a source file listing safely."""
24462451
path = urllib.parse.unquote(path)
24472452
with tokenize.open(path) as fp:
2448-
lines = html.escape(fp.read())
2453+
lines = html_escape(fp.read())
24492454
body = '<pre>%s</pre>' % lines
24502455
heading = html.heading(
24512456
'<big><big><strong>File Listing</strong></big></big>',
@@ -2454,46 +2459,43 @@ def html_getfile(path):
24542459
'File: %s' % path, '#ffffff', '#ee77aa', body)
24552460
return 'getfile %s' % path, contents
24562461

2457-
def html_topics():
2462+
def html_topicindex(title):
24582463
"""Index of topic texts available."""
24592464

24602465
def bltinlink(name):
24612466
return '<a href="topic?key=%s">%s</a>' % (name, name)
24622467

24632468
heading = html.heading(
2464-
'<big><big><strong>INDEX</strong></big></big>',
2469+
'<big><big><strong>%s</strong></big></big>' % title.upper(),
24652470
'#ffffff', '#7799ee')
2466-
names = sorted(Helper.topics.keys())
24672471

2468-
contents = html.multicolumn(names, bltinlink)
2469-
contents = heading + html.bigsection(
2470-
'Topics', '#ffffff', '#ee77aa', contents)
2471-
return 'Topics', contents
2472-
2473-
def html_keywords():
2474-
"""Index of keywords."""
2475-
heading = html.heading(
2476-
'<big><big><strong>INDEX</strong></big></big>',
2477-
'#ffffff', '#7799ee')
2478-
names = sorted(Helper.keywords.keys())
2479-
2480-
def bltinlink(name):
2481-
return '<a href="topic?key=%s">%s</a>' % (name, name)
2472+
keys = {
2473+
'topics': Helper.topics.keys,
2474+
'keywords': Helper.keywords.keys,
2475+
'symbols': Helper.symbols.keys,
2476+
}
2477+
names = sorted(keys[title]())
24822478

24832479
contents = html.multicolumn(names, bltinlink)
24842480
contents = heading + html.bigsection(
2485-
'Keywords', '#ffffff', '#ee77aa', contents)
2486-
return 'Keywords', contents
2481+
title, '#ffffff', '#ee77aa', contents)
2482+
return title, contents
24872483

24882484
def html_topicpage(topic):
24892485
"""Topic or keyword help page."""
24902486
buf = io.StringIO()
24912487
htmlhelp = Helper(buf, buf)
2492-
contents, xrefs = htmlhelp._gettopic(topic)
24932488
if topic in htmlhelp.keywords:
24942489
title = 'KEYWORD'
2495-
else:
2490+
contents, xrefs = htmlhelp._gettopic(topic)
2491+
elif topic in htmlhelp.topics:
24962492
title = 'TOPIC'
2493+
contents, xrefs = htmlhelp._gettopic(topic)
2494+
elif topic in htmlhelp.symbols:
2495+
title = 'SYMBOL'
2496+
contents, xrefs = htmlhelp._getsymbol(topic)
2497+
else:
2498+
raise ValueError('could not find topic %s' % repr(topic))
24972499
heading = html.heading(
24982500
'<big><big><strong>%s</strong></big></big>' % title,
24992501
'#ffffff', '#7799ee')
@@ -2503,7 +2505,7 @@ def html_topicpage(topic):
25032505
xrefs = sorted(xrefs.split())
25042506

25052507
def bltinlink(name):
2506-
return '<a href="topic?key=%s">%s</a>' % (name, name)
2508+
return '<a href="topic?key=%s">%s</a>' % (html_escape(name), html_escape(name))
25072509

25082510
xrefs = html.multicolumn(xrefs, bltinlink)
25092511
xrefs = html.section('Related help topics: ',
@@ -2523,7 +2525,7 @@ def html_error(url, exc):
25232525
heading = html.heading(
25242526
'<big><big><strong>Error</strong></big></big>',
25252527
'#ffffff', '#7799ee')
2526-
contents = '<br>'.join(html.escape(line) for line in
2528+
contents = '<br>'.join(html_escape(line) for line in
25272529
format_exception_only(type(exc), exc))
25282530
contents = heading + html.bigsection(url, '#ffffff', '#bb0000',
25292531
contents)
@@ -2537,23 +2539,21 @@ def get_html_page(url):
25372539
try:
25382540
if url in ("", "index"):
25392541
title, content = html_index()
2540-
elif url == "topics":
2541-
title, content = html_topics()
2542-
elif url == "keywords":
2543-
title, content = html_keywords()
2544-
elif '=' in url:
2545-
op, _, url = url.partition('=')
2546-
if op == "search?key":
2542+
elif url in ("topics", "keywords", "symbols"):
2543+
title, content = html_topicindex(url)
2544+
elif '?key=' in url:
2545+
op, _, url = url.partition('?key=')
2546+
if op == "search":
25472547
title, content = html_search(url)
2548-
elif op == "getfile?key":
2548+
elif op == "getfile":
25492549
title, content = html_getfile(url)
2550-
elif op == "topic?key":
2550+
elif op == "topic":
25512551
# try topics first, then objects.
25522552
try:
25532553
title, content = html_topicpage(url)
25542554
except ValueError:
25552555
title, content = html_getobj(url)
2556-
elif op == "get?key":
2556+
elif op == "get":
25572557
# try objects first, then topics.
25582558
if url in ("", "index"):
25592559
title, content = html_index()
@@ -2747,5 +2747,6 @@ class BadUsage(Exception): pass
27472747
it names a directory, documentation is written for all the contents.
27482748
""".format(cmd=cmd, sep=os.sep))
27492749

2750+
27502751
if __name__ == '__main__':
27512752
cli()

Lib/test/test_pydoc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,9 @@ def test_url_requests(self):
13301330
("", "Pydoc: Index of Modules"),
13311331
("get?key=", "Pydoc: Index of Modules"),
13321332
("index", "Pydoc: Index of Modules"),
1333-
("topics", "Pydoc: Topics"),
1334-
("keywords", "Pydoc: Keywords"),
1333+
("topics", "Pydoc: topics"),
1334+
("keywords", "Pydoc: keywords"),
1335+
("symbols", "Pydoc: symbols"),
13351336
("pydoc", "Pydoc: module pydoc"),
13361337
("get?key=pydoc", "Pydoc: module pydoc"),
13371338
("search?key=pydoc", "Pydoc: Search Results"),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add 'symbols' link to pydoc's html menu bar. Original patch by Ron Adam.
2+
Enhanced by Sanyam Khurana.

0 commit comments

Comments
 (0)