@@ -75,6 +75,7 @@ class or function within a module or module in a package. If the
7575import warnings
7676from annotationlib import Format
7777from collections import deque
78+ from html import escape as html_escape
7879from reprlib import Repr
7980from traceback import format_exception_only
8081
@@ -108,96 +109,10 @@ def pathdirs():
108109 normdirs .append (normdir )
109110 return dirs
110111
111- def _findclass (func ):
112- cls = sys .modules .get (func .__module__ )
113- if cls is None :
114- return None
115- for name in func .__qualname__ .split ('.' )[:- 1 ]:
116- cls = getattr (cls , name )
117- if not inspect .isclass (cls ):
118- return None
119- return cls
120-
121- def _finddoc (obj ):
122- if inspect .ismethod (obj ):
123- name = obj .__func__ .__name__
124- self = obj .__self__
125- if (inspect .isclass (self ) and
126- getattr (getattr (self , name , None ), '__func__' ) is obj .__func__ ):
127- # classmethod
128- cls = self
129- else :
130- cls = self .__class__
131- elif inspect .isfunction (obj ):
132- name = obj .__name__
133- cls = _findclass (obj )
134- if cls is None or getattr (cls , name ) is not obj :
135- return None
136- elif inspect .isbuiltin (obj ):
137- name = obj .__name__
138- self = obj .__self__
139- if (inspect .isclass (self ) and
140- self .__qualname__ + '.' + name == obj .__qualname__ ):
141- # classmethod
142- cls = self
143- else :
144- cls = self .__class__
145- # Should be tested before isdatadescriptor().
146- elif isinstance (obj , property ):
147- name = obj .__name__
148- cls = _findclass (obj .fget )
149- if cls is None or getattr (cls , name ) is not obj :
150- return None
151- elif inspect .ismethoddescriptor (obj ) or inspect .isdatadescriptor (obj ):
152- name = obj .__name__
153- cls = obj .__objclass__
154- if getattr (cls , name ) is not obj :
155- return None
156- if inspect .ismemberdescriptor (obj ):
157- slots = getattr (cls , '__slots__' , None )
158- if isinstance (slots , dict ) and name in slots :
159- return slots [name ]
160- else :
161- return None
162- for base in cls .__mro__ :
163- try :
164- doc = _getowndoc (getattr (base , name ))
165- except AttributeError :
166- continue
167- if doc is not None :
168- return doc
169- return None
170-
171- def _getowndoc (obj ):
172- """Get the documentation string for an object if it is not
173- inherited from its class."""
174- try :
175- doc = object .__getattribute__ (obj , '__doc__' )
176- if doc is None :
177- return None
178- if obj is not type :
179- typedoc = type (obj ).__doc__
180- if isinstance (typedoc , str ) and typedoc == doc :
181- return None
182- return doc
183- except AttributeError :
184- return None
185-
186112def _getdoc (object ):
187- """Get the documentation string for an object.
188-
189- All tabs are expanded to spaces. To clean up docstrings that are
190- indented to line up with blocks of code, any whitespace than can be
191- uniformly removed from the second line onwards is removed."""
192- doc = _getowndoc (object )
193- if doc is None :
194- try :
195- doc = _finddoc (object )
196- except (AttributeError , TypeError ):
197- return None
198- if not isinstance (doc , str ):
199- return None
200- return inspect .cleandoc (doc )
113+ return inspect .getdoc (object ,
114+ fallback_to_class_doc = False ,
115+ inherit_class_doc = False )
201116
202117def getdoc (object ):
203118 """Get the doc string or comments for an object."""
@@ -608,26 +523,26 @@ def repr1(self, x, level):
608523 methodname = 'repr_' + '_' .join (type (x ).__name__ .split ())
609524 if hasattr (self , methodname ):
610525 return getattr (self , methodname )(x , level )
611- return self . escape (cram (stripid (repr (x )), self .maxother ))
526+ return html_escape (cram (stripid (repr (x )), self .maxother ))
612527
613528 def repr_string (self , x , level ):
614529 test = cram (x , self .maxstring )
615530 testrepr = repr (test )
616531 if '\\ ' in test and '\\ ' not in replace (testrepr , r'\\' , '' ):
617532 # Backslashes are only literal in the string and are never
618533 # needed to make any special characters, so show a raw string.
619- return 'r' + testrepr [0 ] + self . escape (test ) + testrepr [0 ]
534+ return 'r' + testrepr [0 ] + html_escape (test ) + testrepr [0 ]
620535 return re .sub (r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)' ,
621536 r'<span class="repr">\1</span>' ,
622- self . escape (testrepr ))
537+ html_escape (testrepr , quote = False ))
623538
624539 repr_str = repr_string
625540
626541 def repr_instance (self , x , level ):
627542 try :
628- return self . escape (cram (stripid (repr (x )), self .maxstring ))
543+ return html_escape (cram (stripid (repr (x )), self .maxstring ))
629544 except :
630- return self . escape ('<%s instance>' % x .__class__ .__name__ )
545+ return html_escape ('<%s instance>' % x .__class__ .__name__ )
631546
632547 repr_unicode = repr_string
633548
@@ -689,7 +604,7 @@ def bigsection(self, title, *args):
689604
690605 def preformat (self , text ):
691606 """Format literal preformatted text."""
692- text = self . escape (text .expandtabs ())
607+ text = html_escape (text .expandtabs (), quote = False )
693608 return replace (text , '\n \n ' , '\n \n ' , '\n \n ' , '\n \n ' ,
694609 ' ' , ' ' , '\n ' , '<br>\n ' )
695610
@@ -767,7 +682,7 @@ def filelink(self, url, path):
767682 def markup (self , text , escape = None , funcs = {}, classes = {}, methods = {}):
768683 """Mark up some plain text, given a context of symbols to look for.
769684 Each context dictionary maps object names to anchor names."""
770- escape = escape or self . escape
685+ escape = escape or html_escape
771686 results = []
772687 here = 0
773688 pattern = re .compile (r'\b((http|https|ftp)://\S+[\w/]|'
@@ -850,9 +765,9 @@ def docmodule(self, object, name=None, mod=None, *ignored):
850765 version = str (object .__version__ )
851766 if version [:11 ] == '$' + 'Revision: ' and version [- 1 :] == '$' :
852767 version = version [11 :- 1 ].strip ()
853- info .append ('version %s' % self . escape (version ))
768+ info .append ('version %s' % html_escape (version ))
854769 if hasattr (object , '__date__' ):
855- info .append (self . escape (str (object .__date__ )))
770+ info .append (html_escape (str (object .__date__ )))
856771 if info :
857772 head = head + ' (%s)' % ', ' .join (info )
858773 docloc = self .getdocloc (object )
@@ -2212,6 +2127,11 @@ def showsymbol(self, symbol):
22122127 topic , _ , xrefs = target .partition (' ' )
22132128 self .showtopic (topic , xrefs )
22142129
2130+ def _getsymbol (self , symbol ):
2131+ target = self .symbols [symbol ]
2132+ topic , _ , xrefs = target .partition (' ' )
2133+ return self ._gettopic (topic , xrefs )
2134+
22152135 def listmodules (self , key = '' ):
22162136 if key :
22172137 self .output .write ('''
@@ -2377,6 +2297,7 @@ def _start_server(urlhandler, hostname, port):
23772297 import email .message
23782298 import select
23792299 import threading
2300+ from urllib .parse import unquote
23802301
23812302 class DocHandler (http .server .BaseHTTPRequestHandler ):
23822303
@@ -2496,11 +2417,14 @@ def page(self, title, contents):
24962417%s</head><body>%s<div style="clear:both;padding-top:.5em;">%s</div>
24972418</body></html>''' % (title , css_link , html_navbar (), contents )
24982419
2420+ def filelink (self , url , path ):
2421+ return ('<a href="getfile?key=%s">%s</a>' %
2422+ (html_escape (url ), html_escape (path )))
24992423
25002424 html = _HTMLDoc ()
25012425
25022426 def html_navbar ():
2503- version = html . escape ("%s [%s, %s]" % (platform .python_version (),
2427+ version = html_escape ("%s [%s, %s]" % (platform .python_version (),
25042428 platform .python_build ()[0 ],
25052429 platform .python_compiler ()))
25062430 return """
@@ -2512,6 +2436,7 @@ def html_navbar():
25122436 <a href="index.html">Module Index</a>
25132437 : <a href="topics.html">Topics</a>
25142438 : <a href="keywords.html">Keywords</a>
2439+ : <a href="symbols.html">Symbols</a>
25152440 </div>
25162441 <div>
25172442 <form action="get" style='display:inline;'>
@@ -2524,7 +2449,7 @@ def html_navbar():
25242449 </form>
25252450 </div>
25262451 </div>
2527- """ % (version , html . escape (platform .platform (terse = True )))
2452+ """ % (version , html_escape (platform .platform (terse = True )))
25282453
25292454 def html_index ():
25302455 """Module Index page."""
@@ -2580,7 +2505,20 @@ def bltinlink(name):
25802505 'key = %s' % key , 'index' , '<br>' .join (results ))
25812506 return 'Search Results' , contents
25822507
2583- def html_topics ():
2508+ def html_getfile (path ):
2509+ """Get and display a source file listing safely."""
2510+ path = urllib .parse .unquote (path )
2511+ with tokenize .open (path ) as fp :
2512+ lines = html_escape (fp .read ())
2513+ body = '<pre>%s</pre>' % lines
2514+ heading = html .heading (
2515+ '<strong class="title">File Listing</strong>' ,
2516+ )
2517+ contents = heading + html .bigsection (
2518+ 'File: %s' % path , 'index' , body )
2519+ return 'getfile %s' % path , contents
2520+
2521+ def html_topicindex (title ):
25842522 """Index of topic texts available."""
25852523
25862524 def bltinlink (name ):
@@ -2589,51 +2527,48 @@ def bltinlink(name):
25892527 heading = html .heading (
25902528 '<strong class="title">INDEX</strong>' ,
25912529 )
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 ())
26052530
2606- def bltinlink (name ):
2607- return '<a href="topic?key=%s">%s</a>' % (name , name )
2531+ keys = {
2532+ 'topics' : Helper .topics .keys ,
2533+ 'keywords' : Helper .keywords .keys ,
2534+ 'symbols' : Helper .symbols .keys ,
2535+ }
2536+ names = sorted (keys [title ]())
26082537
26092538 contents = html .multicolumn (names , bltinlink )
26102539 contents = heading + html .bigsection (
2611- 'Keywords' , 'index' , contents )
2612- return 'Keywords' , contents
2540+ title . capitalize () , 'index' , contents )
2541+ return title . capitalize () , contents
26132542
26142543 def html_topicpage (topic ):
26152544 """Topic or keyword help page."""
26162545 buf = io .StringIO ()
26172546 htmlhelp = Helper (buf , buf )
2618- contents , xrefs = htmlhelp ._gettopic (topic )
26192547 if topic in htmlhelp .keywords :
26202548 title = 'KEYWORD'
2621- else :
2549+ contents , xrefs = htmlhelp ._gettopic (topic )
2550+ elif topic in htmlhelp .topics :
26222551 title = 'TOPIC'
2552+ contents , xrefs = htmlhelp ._gettopic (topic )
2553+ elif topic in htmlhelp .symbols :
2554+ title = 'SYMBOL'
2555+ contents , xrefs = htmlhelp ._getsymbol (topic )
2556+ else :
2557+ raise ValueError (f'could not find topic { topic !r} ' )
26232558 heading = html .heading (
2624- '<strong class="title">%s </strong>' % title ,
2559+ f '<strong class="title">{ title } </strong>' ,
26252560 )
2626- contents = '<pre>%s</pre>' % html .markup (contents )
2561+ contents = f '<pre>{ html .markup (contents )} </pre>'
26272562 contents = html .bigsection (topic , 'index' , contents )
26282563 if xrefs :
26292564 xrefs = sorted (xrefs .split ())
26302565
26312566 def bltinlink (name ):
2632- return '<a href="topic?key=%s">%s </a>' % ( name , name )
2567+ return f '<a href="topic?key={ html_escape ( name ) } > { html_escape ( name ) } </a>'
26332568
26342569 xrefs = html .multicolumn (xrefs , bltinlink )
26352570 xrefs = html .section ('Related help topics: ' , 'index' , xrefs )
2636- return ('%s %s' % ( title , topic ) ,
2571+ return (f' { title } { topic } ' ,
26372572 '' .join ((heading , contents , xrefs )))
26382573
26392574 def html_getobj (url ):
@@ -2648,7 +2583,7 @@ def html_error(url, exc):
26482583 heading = html .heading (
26492584 '<strong class="title">Error</strong>' ,
26502585 )
2651- contents = '<br>' .join (html . escape (line ) for line in
2586+ contents = '<br>' .join (html_escape (line ) for line in
26522587 format_exception_only (type (exc ), exc ))
26532588 contents = heading + html .bigsection (url , 'error' , contents )
26542589 return "Error - %s" % url , contents
@@ -2661,21 +2596,21 @@ def get_html_page(url):
26612596 try :
26622597 if url in ("" , "index" ):
26632598 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" :
2599+ elif url in ("topics" , "keywords" , "symbols" ):
2600+ title , content = html_topicindex (url )
2601+ elif '?key=' in url :
2602+ op , _ , url = url .partition ('?key=' )
2603+ if op == "search" :
26712604 title , content = html_search (url )
2672- elif op == "topic?key" :
2605+ elif op == "getfile" :
2606+ title , content = html_getfile (url )
2607+ elif op == "topic" :
26732608 # try topics first, then objects.
26742609 try :
26752610 title , content = html_topicpage (url )
26762611 except ValueError :
26772612 title , content = html_getobj (url )
2678- elif op == "get?key " :
2613+ elif op == "get" :
26792614 # try objects first, then topics.
26802615 if url in ("" , "index" ):
26812616 title , content = html_index ()
@@ -2870,5 +2805,6 @@ class BadUsage(Exception): pass
28702805 it names a directory, documentation is written for all the contents.
28712806""" .format (cmd = cmd , sep = os .sep ))
28722807
2808+
28732809if __name__ == '__main__' :
28742810 cli ()
0 commit comments