import os
import sys
from html.parser import HTMLParser
from os.path import abspath, dirname, isfile, join
from platform import python_version
from tkinter import Toplevel, Text, Menu
from tkinter.ttk import Frame, Menubutton, Scrollbar, Style
from tkinter import font as tkfont
from idlelib.config import idleConf
from idlelib.colorizer import color_config
class HelpParser(HTMLParser):
def __init__(self, text):
HTMLParser.__init__(self, convert_charrefs=True)
self.text = text self.tags = '' self.chartags = '' self.hdrlink = False self.level = 0 self.pre = False self.hprefix = '' self.nested_dl = False self.simplelist = False self.toc = [] self.header = '' self.prevtag = None
def indent(self, amt=1):
"Change indent (+1, 0, -1) and tags."
self.level += amt
self.tags = '' if self.level == 0 else 'l'+str(self.level)
def handle_starttag(self, tag, attrs):
"Handle starttags in help.html."
class_ = ''
for a, v in attrs:
if a == 'class':
class_ = v
s = ''
if tag == 'p' and self.prevtag and not self.prevtag[0]:
lastline = self.text.get('end-1c linestart', 'end-1c')
s = '\n\n' if lastline and not lastline.isspace() else '\n'
elif tag == 'span' and class_ == 'pre':
self.chartags = 'pre'
elif tag == 'span' and class_ == 'versionmodified':
self.chartags = 'em'
elif tag == 'em':
self.chartags = 'em'
elif tag in ['ul', 'ol']:
if class_.find('simple') != -1:
s = '\n'
self.simplelist = True
else:
self.simplelist = False
self.indent()
elif tag == 'dl':
if self.level > 0:
self.nested_dl = True
elif tag == 'li':
s = '\n* '
elif tag == 'dt':
s = '\n\n' if not self.nested_dl else '\n' self.nested_dl = False
elif tag == 'dd':
self.indent()
s = '\n'
elif tag == 'pre':
self.pre = True
self.text.insert('end', '\n\n')
self.tags = 'preblock'
elif tag == 'a' and class_ == 'headerlink':
self.hdrlink = True
elif tag == 'h1':
self.tags = tag
elif tag in ['h2', 'h3']:
self.header = ''
self.text.insert('end', '\n\n')
self.tags = tag
self.text.insert('end', s, (self.tags, self.chartags))
self.prevtag = (True, tag)
def handle_endtag(self, tag):
"Handle endtags in help.html."
if tag in ['h1', 'h2', 'h3']:
assert self.level == 0
indent = (' ' if tag == 'h3' else
' ' if tag == 'h2' else
'')
self.toc.append((indent+self.header, self.text.index('insert')))
self.tags = ''
elif tag in ['span', 'em']:
self.chartags = ''
elif tag == 'a':
self.hdrlink = False
elif tag == 'pre':
self.pre = False
self.tags = ''
elif tag in ['ul', 'dd', 'ol']:
self.indent(-1)
self.prevtag = (False, tag)
def handle_data(self, data):
"Handle date segments in help.html."
if not self.hdrlink:
d = data if self.pre else data.replace('\n', ' ')
if self.tags == 'h1':
try:
self.hprefix = d[:d.index(' ')]
if not self.hprefix.isdigit():
self.hprefix = ''
except ValueError:
self.hprefix = ''
if self.tags in ['h1', 'h2', 'h3']:
if (self.hprefix != '' and
d[0:len(self.hprefix)] == self.hprefix):
d = d[len(self.hprefix):]
self.header += d.strip()
self.text.insert('end', d, (self.tags, self.chartags))
class HelpText(Text):
"Display help.html."
def __init__(self, parent, filename):
"Configure tags and feed file to parser."
uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
uhigh = 3 * uhigh // 4 Text.__init__(self, parent, wrap='word', highlightthickness=0,
padx=5, borderwidth=0, width=uwide, height=uhigh)
normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
color_config(self)
self['font'] = (normalfont, 12)
self.tag_configure('em', font=(normalfont, 12, 'italic'))
self.tag_configure('h1', font=(normalfont, 20, 'bold'))
self.tag_configure('h2', font=(normalfont, 18, 'bold'))
self.tag_configure('h3', font=(normalfont, 15, 'bold'))
self.tag_configure('pre', font=(fixedfont, 12))
preback = self['selectbackground']
self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
background=preback)
self.tag_configure('l1', lmargin1=25, lmargin2=25)
self.tag_configure('l2', lmargin1=50, lmargin2=50)
self.tag_configure('l3', lmargin1=75, lmargin2=75)
self.tag_configure('l4', lmargin1=100, lmargin2=100)
self.parser = HelpParser(self)
with open(filename, encoding='utf-8') as f:
contents = f.read()
self.parser.feed(contents)
self['state'] = 'disabled'
def findfont(self, names):
"Return name of first font family derived from names."
for name in names:
if name.lower() in (x.lower() for x in tkfont.names(root=self)):
font = tkfont.Font(name=name, exists=True, root=self)
return font.actual()['family']
elif name.lower() in (x.lower()
for x in tkfont.families(root=self)):
return name
class HelpFrame(Frame):
"Display html text, scrollbar, and toc."
def __init__(self, parent, filename):
Frame.__init__(self, parent)
self.text = text = HelpText(self, filename)
self.style = Style(parent)
self['style'] = 'helpframe.TFrame'
self.style.configure('helpframe.TFrame', background=text['background'])
self.toc = toc = self.toc_menu(text)
self.scroll = scroll = Scrollbar(self, command=text.yview)
text['yscrollcommand'] = scroll.set
self.rowconfigure(0, weight=1)
self.columnconfigure(1, weight=1) toc.grid(row=0, column=0, sticky='nw')
text.grid(row=0, column=1, sticky='nsew')
scroll.grid(row=0, column=2, sticky='ns')
def toc_menu(self, text):
"Create table of contents as drop-down menu."
toc = Menubutton(self, text='TOC')
drop = Menu(toc, tearoff=False)
for lbl, dex in text.parser.toc:
drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
toc['menu'] = drop
return toc
class HelpWindow(Toplevel):
"Display frame with rendered html."
def __init__(self, parent, filename, title):
Toplevel.__init__(self, parent)
self.wm_title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy)
self.frame = HelpFrame(self, filename)
self.frame.grid(column=0, row=0, sticky='nsew')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
def copy_strip():
src = join(abspath(dirname(dirname(dirname(__file__)))),
'Doc', 'build', 'html', 'library', 'idle.html')
dst = join(abspath(dirname(__file__)), 'help.html')
with open(src, 'r', encoding="utf-8") as inn, open(dst, 'w', encoding="utf-8") as out:
copy = False
for line in inn:
if '<section id="idle">' in line: copy = True
if '<div class="clearer">' in line: break
if copy: out.write(line.strip() + '\n')
print(f'{src} copied to {dst}')
def show_idlehelp(parent):
"Create HelpWindow; called from Idle Help event handler."
filename = join(abspath(dirname(__file__)), 'help.html')
if not isfile(filename): return
return HelpWindow(parent, filename, 'IDLE Doc (%s)' % python_version())
def _get_dochome():
"Return path to local docs if present, otherwise link to docs.python.org."
dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
if sys.platform.count('linux'):
pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
if os.path.isdir('/var/www/html/python/'): dochome = '/var/www/html/python/index.html'
else:
basepath = '/usr/share/doc/' dochome = os.path.join(basepath, pyver, 'Doc', 'index.html')
elif sys.platform[:3] == 'win':
import winreg docfile = ''
KEY = (rf"Software\Python\PythonCore\{sys.winver}"
r"\Help\Main Python Documentation")
try:
docfile = winreg.QueryValue(winreg.HKEY_CURRENT_USER, KEY)
except FileNotFoundError:
try:
docfile = winreg.QueryValue(winreg.HKEY_LOCAL_MACHINE, KEY)
except FileNotFoundError:
pass
if os.path.isfile(docfile):
dochome = docfile
elif sys.platform == 'darwin':
dochome = os.path.join(sys.base_prefix,
'Resources/English.lproj/Documentation/index.html')
dochome = os.path.normpath(dochome)
if os.path.isfile(dochome):
if sys.platform == 'darwin':
return 'file://' + dochome
return dochome
else:
return "https://docs.python.org/%d.%d/" % sys.version_info[:2]
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_help', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(show_idlehelp)