ยปCore Development>Code coverage>Lib/idlelib/help.py

Python code coverage for Lib/idlelib/help.py

#countcontent
1n/a""" help.py: Implement the Idle help menu.
2n/aContents are subject to revision at any time, without notice.
3n/a
4n/a
5n/aHelp => About IDLE: diplay About Idle dialog
6n/a
7n/a<to be moved here from help_about.py>
8n/a
9n/a
10n/aHelp => IDLE Help: Display help.html with proper formatting.
11n/aDoc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
12n/a(help.copy_strip)=> Lib/idlelib/help.html
13n/a
14n/aHelpParser - Parse help.html and render to tk Text.
15n/a
16n/aHelpText - Display formatted help.html.
17n/a
18n/aHelpFrame - Contain text, scrollbar, and table-of-contents.
19n/a(This will be needed for display in a future tabbed window.)
20n/a
21n/aHelpWindow - Display HelpFrame in a standalone window.
22n/a
23n/acopy_strip - Copy idle.html to help.html, rstripping each line.
24n/a
25n/ashow_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
26n/a"""
27n/afrom html.parser import HTMLParser
28n/afrom os.path import abspath, dirname, isfile, join
29n/afrom platform import python_version
30n/a
31n/afrom tkinter import Toplevel, Frame, Text, Menu
32n/afrom tkinter.ttk import Menubutton, Scrollbar
33n/afrom tkinter import font as tkfont
34n/a
35n/afrom idlelib.config import idleConf
36n/a
37n/a## About IDLE ##
38n/a
39n/a
40n/a## IDLE Help ##
41n/a
42n/aclass HelpParser(HTMLParser):
43n/a """Render help.html into a text widget.
44n/a
45n/a The overridden handle_xyz methods handle a subset of html tags.
46n/a The supplied text should have the needed tag configurations.
47n/a The behavior for unsupported tags, such as table, is undefined.
48n/a If the tags generated by Sphinx change, this class, especially
49n/a the handle_starttag and handle_endtags methods, might have to also.
50n/a """
51n/a def __init__(self, text):
52n/a HTMLParser.__init__(self, convert_charrefs=True)
53n/a self.text = text # text widget we're rendering into
54n/a self.tags = '' # current block level text tags to apply
55n/a self.chartags = '' # current character level text tags
56n/a self.show = False # used so we exclude page navigation
57n/a self.hdrlink = False # used so we don't show header links
58n/a self.level = 0 # indentation level
59n/a self.pre = False # displaying preformatted text
60n/a self.hprefix = '' # prefix such as '25.5' to strip from headings
61n/a self.nested_dl = False # if we're in a nested <dl>
62n/a self.simplelist = False # simple list (no double spacing)
63n/a self.toc = [] # pair headers with text indexes for toc
64n/a self.header = '' # text within header tags for toc
65n/a
66n/a def indent(self, amt=1):
67n/a self.level += amt
68n/a self.tags = '' if self.level == 0 else 'l'+str(self.level)
69n/a
70n/a def handle_starttag(self, tag, attrs):
71n/a "Handle starttags in help.html."
72n/a class_ = ''
73n/a for a, v in attrs:
74n/a if a == 'class':
75n/a class_ = v
76n/a s = ''
77n/a if tag == 'div' and class_ == 'section':
78n/a self.show = True # start of main content
79n/a elif tag == 'div' and class_ == 'sphinxsidebar':
80n/a self.show = False # end of main content
81n/a elif tag == 'p' and class_ != 'first':
82n/a s = '\n\n'
83n/a elif tag == 'span' and class_ == 'pre':
84n/a self.chartags = 'pre'
85n/a elif tag == 'span' and class_ == 'versionmodified':
86n/a self.chartags = 'em'
87n/a elif tag == 'em':
88n/a self.chartags = 'em'
89n/a elif tag in ['ul', 'ol']:
90n/a if class_.find('simple') != -1:
91n/a s = '\n'
92n/a self.simplelist = True
93n/a else:
94n/a self.simplelist = False
95n/a self.indent()
96n/a elif tag == 'dl':
97n/a if self.level > 0:
98n/a self.nested_dl = True
99n/a elif tag == 'li':
100n/a s = '\n* ' if self.simplelist else '\n\n* '
101n/a elif tag == 'dt':
102n/a s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
103n/a self.nested_dl = False
104n/a elif tag == 'dd':
105n/a self.indent()
106n/a s = '\n'
107n/a elif tag == 'pre':
108n/a self.pre = True
109n/a if self.show:
110n/a self.text.insert('end', '\n\n')
111n/a self.tags = 'preblock'
112n/a elif tag == 'a' and class_ == 'headerlink':
113n/a self.hdrlink = True
114n/a elif tag == 'h1':
115n/a self.tags = tag
116n/a elif tag in ['h2', 'h3']:
117n/a if self.show:
118n/a self.header = ''
119n/a self.text.insert('end', '\n\n')
120n/a self.tags = tag
121n/a if self.show:
122n/a self.text.insert('end', s, (self.tags, self.chartags))
123n/a
124n/a def handle_endtag(self, tag):
125n/a "Handle endtags in help.html."
126n/a if tag in ['h1', 'h2', 'h3']:
127n/a self.indent(0) # clear tag, reset indent
128n/a if self.show:
129n/a self.toc.append((self.header, self.text.index('insert')))
130n/a elif tag in ['span', 'em']:
131n/a self.chartags = ''
132n/a elif tag == 'a':
133n/a self.hdrlink = False
134n/a elif tag == 'pre':
135n/a self.pre = False
136n/a self.tags = ''
137n/a elif tag in ['ul', 'dd', 'ol']:
138n/a self.indent(amt=-1)
139n/a
140n/a def handle_data(self, data):
141n/a "Handle date segments in help.html."
142n/a if self.show and not self.hdrlink:
143n/a d = data if self.pre else data.replace('\n', ' ')
144n/a if self.tags == 'h1':
145n/a self.hprefix = d[0:d.index(' ')]
146n/a if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
147n/a if d[0:len(self.hprefix)] == self.hprefix:
148n/a d = d[len(self.hprefix):].strip()
149n/a self.header += d
150n/a self.text.insert('end', d, (self.tags, self.chartags))
151n/a
152n/a
153n/aclass HelpText(Text):
154n/a "Display help.html."
155n/a def __init__(self, parent, filename):
156n/a "Configure tags and feed file to parser."
157n/a uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
158n/a uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
159n/a uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
160n/a Text.__init__(self, parent, wrap='word', highlightthickness=0,
161n/a padx=5, borderwidth=0, width=uwide, height=uhigh)
162n/a
163n/a normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
164n/a fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
165n/a self['font'] = (normalfont, 12)
166n/a self.tag_configure('em', font=(normalfont, 12, 'italic'))
167n/a self.tag_configure('h1', font=(normalfont, 20, 'bold'))
168n/a self.tag_configure('h2', font=(normalfont, 18, 'bold'))
169n/a self.tag_configure('h3', font=(normalfont, 15, 'bold'))
170n/a self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
171n/a self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
172n/a borderwidth=1, relief='solid', background='#eeffcc')
173n/a self.tag_configure('l1', lmargin1=25, lmargin2=25)
174n/a self.tag_configure('l2', lmargin1=50, lmargin2=50)
175n/a self.tag_configure('l3', lmargin1=75, lmargin2=75)
176n/a self.tag_configure('l4', lmargin1=100, lmargin2=100)
177n/a
178n/a self.parser = HelpParser(self)
179n/a with open(filename, encoding='utf-8') as f:
180n/a contents = f.read()
181n/a self.parser.feed(contents)
182n/a self['state'] = 'disabled'
183n/a
184n/a def findfont(self, names):
185n/a "Return name of first font family derived from names."
186n/a for name in names:
187n/a if name.lower() in (x.lower() for x in tkfont.names(root=self)):
188n/a font = tkfont.Font(name=name, exists=True, root=self)
189n/a return font.actual()['family']
190n/a elif name.lower() in (x.lower()
191n/a for x in tkfont.families(root=self)):
192n/a return name
193n/a
194n/a
195n/aclass HelpFrame(Frame):
196n/a "Display html text, scrollbar, and toc."
197n/a def __init__(self, parent, filename):
198n/a Frame.__init__(self, parent)
199n/a # keep references to widgets for test access.
200n/a self.text = text = HelpText(self, filename)
201n/a self['background'] = text['background']
202n/a self.toc = toc = self.toc_menu(text)
203n/a self.scroll = scroll = Scrollbar(self, command=text.yview)
204n/a text['yscrollcommand'] = scroll.set
205n/a
206n/a self.rowconfigure(0, weight=1)
207n/a self.columnconfigure(1, weight=1) # text
208n/a toc.grid(row=0, column=0, sticky='nw')
209n/a text.grid(row=0, column=1, sticky='nsew')
210n/a scroll.grid(row=0, column=2, sticky='ns')
211n/a
212n/a def toc_menu(self, text):
213n/a "Create table of contents as drop-down menu."
214n/a toc = Menubutton(self, text='TOC')
215n/a drop = Menu(toc, tearoff=False)
216n/a for lbl, dex in text.parser.toc:
217n/a drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
218n/a toc['menu'] = drop
219n/a return toc
220n/a
221n/a
222n/aclass HelpWindow(Toplevel):
223n/a "Display frame with rendered html."
224n/a def __init__(self, parent, filename, title):
225n/a Toplevel.__init__(self, parent)
226n/a self.wm_title(title)
227n/a self.protocol("WM_DELETE_WINDOW", self.destroy)
228n/a HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
229n/a self.grid_columnconfigure(0, weight=1)
230n/a self.grid_rowconfigure(0, weight=1)
231n/a
232n/a
233n/adef copy_strip():
234n/a """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
235n/a
236n/a Files with trailing whitespace cannot be pushed to the hg cpython
237n/a repository. For 3.x (on Windows), help.html is generated, after
238n/a editing idle.rst in the earliest maintenance version, with
239n/a sphinx-build -bhtml . build/html
240n/a python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
241n/a After refreshing TortoiseHG workshop to generate a diff,
242n/a check both the diff and displayed text. Push the diff along with
243n/a the idle.rst change and merge both into default (or an intermediate
244n/a maintenance version).
245n/a
246n/a When the 'earlist' version gets its final maintenance release,
247n/a do an update as described above, without editing idle.rst, to
248n/a rebase help.html on the next version of idle.rst. Do not worry
249n/a about version changes as version is not displayed. Examine other
250n/a changes and the result of Help -> IDLE Help.
251n/a
252n/a If maintenance and default versions of idle.rst diverge, and
253n/a merging does not go smoothly, then consider generating
254n/a separate help.html files from separate idle.htmls.
255n/a """
256n/a src = join(abspath(dirname(dirname(dirname(__file__)))),
257n/a 'Doc', 'build', 'html', 'library', 'idle.html')
258n/a dst = join(abspath(dirname(__file__)), 'help.html')
259n/a with open(src, 'rb') as inn,\
260n/a open(dst, 'wb') as out:
261n/a for line in inn:
262n/a out.write(line.rstrip() + b'\n')
263n/a print('idle.html copied to help.html')
264n/a
265n/adef show_idlehelp(parent):
266n/a "Create HelpWindow; called from Idle Help event handler."
267n/a filename = join(abspath(dirname(__file__)), 'help.html')
268n/a if not isfile(filename):
269n/a # try copy_strip, present message
270n/a return
271n/a HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
272n/a
273n/aif __name__ == '__main__':
274n/a from idlelib.idle_test.htest import run
275n/a run(show_idlehelp)