Skip to content

Commit e70e84c

Browse files
bpo-18387: Add 'symbols' link to pydoc's html menu bar
1 parent f575dd9 commit e70e84c

File tree

3 files changed

+67
-46
lines changed

3 files changed

+67
-46
lines changed

Lib/pydoc.py

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class or function within a module or module in a package. If the
7575
import warnings
7676
from annotationlib import Format
7777
from collections import deque
78+
from html import escape as html_escape
7879
from reprlib import Repr
7980
from traceback import format_exception_only
8081

@@ -597,9 +598,6 @@ def __init__(self):
597598
self.maxdict = 10
598599
self.maxstring = self.maxother = 100
599600

600-
def escape(self, text):
601-
return replace(text, '&', '&amp;', '<', '&lt;', '>', '&gt;')
602-
603601
def repr(self, object):
604602
return Repr.repr(self, object)
605603

@@ -608,26 +606,26 @@ def repr1(self, x, level):
608606
methodname = 'repr_' + '_'.join(type(x).__name__.split())
609607
if hasattr(self, methodname):
610608
return getattr(self, methodname)(x, level)
611-
return self.escape(cram(stripid(repr(x)), self.maxother))
609+
return html_escape(cram(stripid(repr(x)), self.maxother))
612610

613611
def repr_string(self, x, level):
614612
test = cram(x, self.maxstring)
615613
testrepr = repr(test)
616614
if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
617615
# Backslashes are only literal in the string and are never
618616
# needed to make any special characters, so show a raw string.
619-
return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
617+
return 'r' + testrepr[0] + html_escape(test) + testrepr[0]
620618
return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
621619
r'<span class="repr">\1</span>',
622-
self.escape(testrepr))
620+
html_escape(testrepr, quote=False))
623621

624622
repr_str = repr_string
625623

626624
def repr_instance(self, x, level):
627625
try:
628-
return self.escape(cram(stripid(repr(x)), self.maxstring))
626+
return html_escape(cram(stripid(repr(x)), self.maxstring))
629627
except:
630-
return self.escape('<%s instance>' % x.__class__.__name__)
628+
return html_escape('<%s instance>' % x.__class__.__name__)
631629

632630
repr_unicode = repr_string
633631

@@ -638,7 +636,6 @@ class HTMLDoc(Doc):
638636

639637
_repr_instance = HTMLRepr()
640638
repr = _repr_instance.repr
641-
escape = _repr_instance.escape
642639

643640
def page(self, title, contents):
644641
"""Format an HTML page."""
@@ -689,7 +686,7 @@ def bigsection(self, title, *args):
689686

690687
def preformat(self, text):
691688
"""Format literal preformatted text."""
692-
text = self.escape(text.expandtabs())
689+
text = html_escape(text.expandtabs(), quote=False)
693690
return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
694691
' ', '&nbsp;', '\n', '<br>\n')
695692

@@ -767,7 +764,7 @@ def filelink(self, url, path):
767764
def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
768765
"""Mark up some plain text, given a context of symbols to look for.
769766
Each context dictionary maps object names to anchor names."""
770-
escape = escape or self.escape
767+
escape = escape or html_escape
771768
results = []
772769
here = 0
773770
pattern = re.compile(r'\b((http|https|ftp)://\S+[\w/]|'
@@ -850,9 +847,9 @@ def docmodule(self, object, name=None, mod=None, *ignored):
850847
version = str(object.__version__)
851848
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
852849
version = version[11:-1].strip()
853-
info.append('version %s' % self.escape(version))
850+
info.append('version %s' % html_escape(version))
854851
if hasattr(object, '__date__'):
855-
info.append(self.escape(str(object.__date__)))
852+
info.append(html_escape(str(object.__date__)))
856853
if info:
857854
head = head + ' (%s)' % ', '.join(info)
858855
docloc = self.getdocloc(object)
@@ -2212,6 +2209,11 @@ def showsymbol(self, symbol):
22122209
topic, _, xrefs = target.partition(' ')
22132210
self.showtopic(topic, xrefs)
22142211

2212+
def _getsymbol(self, symbol):
2213+
target = self.symbols[symbol]
2214+
topic, _, xrefs = target.partition(' ')
2215+
return self._gettopic(topic, xrefs)
2216+
22152217
def listmodules(self, key=''):
22162218
if key:
22172219
self.output.write('''
@@ -2377,6 +2379,7 @@ def _start_server(urlhandler, hostname, port):
23772379
import email.message
23782380
import select
23792381
import threading
2382+
from urllib.parse import unquote
23802383

23812384
class DocHandler(http.server.BaseHTTPRequestHandler):
23822385

@@ -2496,11 +2499,14 @@ def page(self, title, contents):
24962499
%s</head><body>%s<div style="clear:both;padding-top:.5em;">%s</div>
24972500
</body></html>''' % (title, css_link, html_navbar(), contents)
24982501

2502+
def filelink(self, url, path):
2503+
return ('<a href="getfile?key=%s">%s</a>' %
2504+
(html_escape(url), html_escape(path)))
24992505

25002506
html = _HTMLDoc()
25012507

25022508
def html_navbar():
2503-
version = html.escape("%s [%s, %s]" % (platform.python_version(),
2509+
version = html_escape("%s [%s, %s]" % (platform.python_version(),
25042510
platform.python_build()[0],
25052511
platform.python_compiler()))
25062512
return """
@@ -2512,6 +2518,7 @@ def html_navbar():
25122518
<a href="index.html">Module Index</a>
25132519
: <a href="topics.html">Topics</a>
25142520
: <a href="keywords.html">Keywords</a>
2521+
: <a href="symbols.html">Symbols</a>
25152522
</div>
25162523
<div>
25172524
<form action="get" style='display:inline;'>
@@ -2524,7 +2531,7 @@ def html_navbar():
25242531
</form>
25252532
</div>
25262533
</div>
2527-
""" % (version, html.escape(platform.platform(terse=True)))
2534+
""" % (version, html_escape(platform.platform(terse=True)))
25282535

25292536
def html_index():
25302537
"""Module Index page."""
@@ -2580,7 +2587,20 @@ def bltinlink(name):
25802587
'key = %s' % key, 'index', '<br>'.join(results))
25812588
return 'Search Results', contents
25822589

2583-
def html_topics():
2590+
def html_getfile(path):
2591+
"""Get and display a source file listing safely."""
2592+
path = urllib.parse.unquote(path)
2593+
with tokenize.open(path) as fp:
2594+
lines = html_escape(fp.read())
2595+
body = '<pre>%s</pre>' % lines
2596+
heading = html.heading(
2597+
'<strong class="title">File Listing</strong>',
2598+
)
2599+
contents = heading + html.bigsection(
2600+
'File: %s' % path, 'index', body)
2601+
return 'getfile %s' % path, contents
2602+
2603+
def html_topicindex(title):
25842604
"""Index of topic texts available."""
25852605

25862606
def bltinlink(name):
@@ -2589,37 +2609,34 @@ def bltinlink(name):
25892609
heading = html.heading(
25902610
'<strong class="title">INDEX</strong>',
25912611
)
2592-
names = sorted(Helper.topics.keys())
2593-
2594-
contents = html.multicolumn(names, bltinlink)
2595-
contents = heading + html.bigsection(
2596-
'Topics', 'index', contents)
2597-
return 'Topics', contents
2598-
2599-
def html_keywords():
2600-
"""Index of keywords."""
2601-
heading = html.heading(
2602-
'<strong class="title">INDEX</strong>',
2603-
)
2604-
names = sorted(Helper.keywords.keys())
26052612

2606-
def bltinlink(name):
2607-
return '<a href="topic?key=%s">%s</a>' % (name, name)
2613+
keys = {
2614+
'topics': Helper.topics.keys,
2615+
'keywords': Helper.keywords.keys,
2616+
'symbols': Helper.symbols.keys,
2617+
}
2618+
names = sorted(keys[title]())
26082619

26092620
contents = html.multicolumn(names, bltinlink)
26102621
contents = heading + html.bigsection(
2611-
'Keywords', 'index', contents)
2612-
return 'Keywords', contents
2622+
title.capitalize(), 'index', contents)
2623+
return title.capitalize(), contents
26132624

26142625
def html_topicpage(topic):
26152626
"""Topic or keyword help page."""
26162627
buf = io.StringIO()
26172628
htmlhelp = Helper(buf, buf)
2618-
contents, xrefs = htmlhelp._gettopic(topic)
26192629
if topic in htmlhelp.keywords:
26202630
title = 'KEYWORD'
2621-
else:
2631+
contents, xrefs = htmlhelp._gettopic(topic)
2632+
elif topic in htmlhelp.topics:
26222633
title = 'TOPIC'
2634+
contents, xrefs = htmlhelp._gettopic(topic)
2635+
elif topic in htmlhelp.symbols:
2636+
title = 'SYMBOL'
2637+
contents, xrefs = htmlhelp._getsymbol(topic)
2638+
else:
2639+
raise ValueError('could not find topic %s' % repr(topic))
26232640
heading = html.heading(
26242641
'<strong class="title">%s</strong>' % title,
26252642
)
@@ -2629,7 +2646,7 @@ def html_topicpage(topic):
26292646
xrefs = sorted(xrefs.split())
26302647

26312648
def bltinlink(name):
2632-
return '<a href="topic?key=%s">%s</a>' % (name, name)
2649+
return '<a href="topic?key=%s">%s</a>' % (html_escape(name), html_escape(name))
26332650

26342651
xrefs = html.multicolumn(xrefs, bltinlink)
26352652
xrefs = html.section('Related help topics: ', 'index', xrefs)
@@ -2648,7 +2665,7 @@ def html_error(url, exc):
26482665
heading = html.heading(
26492666
'<strong class="title">Error</strong>',
26502667
)
2651-
contents = '<br>'.join(html.escape(line) for line in
2668+
contents = '<br>'.join(html_escape(line) for line in
26522669
format_exception_only(type(exc), exc))
26532670
contents = heading + html.bigsection(url, 'error', contents)
26542671
return "Error - %s" % url, contents
@@ -2661,21 +2678,21 @@ def get_html_page(url):
26612678
try:
26622679
if url in ("", "index"):
26632680
title, content = html_index()
2664-
elif url == "topics":
2665-
title, content = html_topics()
2666-
elif url == "keywords":
2667-
title, content = html_keywords()
2668-
elif '=' in url:
2669-
op, _, url = url.partition('=')
2670-
if op == "search?key":
2681+
elif url in ("topics", "keywords", "symbols"):
2682+
title, content = html_topicindex(url)
2683+
elif '?key=' in url:
2684+
op, _, url = url.partition('?key=')
2685+
if op == "search":
26712686
title, content = html_search(url)
2672-
elif op == "topic?key":
2687+
elif op == "getfile":
2688+
title, content = html_getfile(url)
2689+
elif op == "topic":
26732690
# try topics first, then objects.
26742691
try:
26752692
title, content = html_topicpage(url)
26762693
except ValueError:
26772694
title, content = html_getobj(url)
2678-
elif op == "get?key":
2695+
elif op == "get":
26792696
# try objects first, then topics.
26802697
if url in ("", "index"):
26812698
title, content = html_index()
@@ -2870,5 +2887,6 @@ class BadUsage(Exception): pass
28702887
it names a directory, documentation is written for all the contents.
28712888
""".format(cmd=cmd, sep=os.sep))
28722889

2890+
28732891
if __name__ == '__main__':
28742892
cli()

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,7 @@ def test_url_requests(self):
21022102
("index", "Pydoc: Index of Modules"),
21032103
("topics", "Pydoc: Topics"),
21042104
("keywords", "Pydoc: Keywords"),
2105+
("symbols", "Pydoc: Symbols"),
21052106
("pydoc", "Pydoc: module pydoc"),
21062107
("get?key=pydoc", "Pydoc: module pydoc"),
21072108
("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)