source: python/trunk/Tools/webchecker/wcgui.py@ 6

Last change on this file since 6 was 2, checked in by Yuri Dario, 15 years ago

Initial import for vendor code.

  • Property svn:eol-style set to native
File size: 14.9 KB
Line 
1#! /usr/bin/env python
2
3"""GUI interface to webchecker.
4
5This works as a Grail applet too! E.g.
6
7 <APPLET CODE=wcgui.py NAME=CheckerWindow></APPLET>
8
9Checkpoints are not (yet??? ever???) supported.
10
11User interface:
12
13Enter a root to check in the text entry box. To enter more than one root,
14enter them one at a time and press <Return> for each one.
15
16Command buttons Start, Stop and "Check one" govern the checking process in
17the obvious way. Start and "Check one" also enter the root from the text
18entry box if one is present. There's also a check box (enabled by default)
19to decide whether actually to follow external links (since this can slow
20the checking down considerably). Finally there's a Quit button.
21
22A series of checkbuttons determines whether the corresponding output panel
23is shown. List panels are also automatically shown or hidden when their
24status changes between empty to non-empty. There are six panels:
25
26Log -- raw output from the checker (-v, -q affect this)
27To check -- links discovered but not yet checked
28Checked -- links that have been checked
29Bad links -- links that failed upon checking
30Errors -- pages containing at least one bad link
31Details -- details about one URL; double click on a URL in any of
32 the above list panels (not in Log) will show details
33 for that URL
34
35Use your window manager's Close command to quit.
36
37Command line options:
38
39-m bytes -- skip HTML pages larger than this size (default %(MAXPAGE)d)
40-q -- quiet operation (also suppresses external links report)
41-v -- verbose operation; repeating -v will increase verbosity
42-t root -- specify root dir which should be treated as internal (can repeat)
43-a -- don't check name anchors
44
45Command line arguments:
46
47rooturl -- URL to start checking
48 (default %(DEFROOT)s)
49
50XXX The command line options (-m, -q, -v) should be GUI accessible.
51
52XXX The roots should be visible as a list (?).
53
54XXX The multipanel user interface is clumsy.
55
56"""
57
58# ' Emacs bait
59
60
61import sys
62import getopt
63from Tkinter import *
64import tktools
65import webchecker
66
67# Override some for a weaker platform
68if sys.platform == 'mac':
69 webchecker.DEFROOT = "http://grail.cnri.reston.va.us/"
70 webchecker.MAXPAGE = 50000
71 webchecker.verbose = 4
72
73def main():
74 try:
75 opts, args = getopt.getopt(sys.argv[1:], 't:m:qva')
76 except getopt.error, msg:
77 sys.stdout = sys.stderr
78 print msg
79 print __doc__%vars(webchecker)
80 sys.exit(2)
81 webchecker.verbose = webchecker.VERBOSE
82 webchecker.nonames = webchecker.NONAMES
83 webchecker.maxpage = webchecker.MAXPAGE
84 extra_roots = []
85 for o, a in opts:
86 if o == '-m':
87 webchecker.maxpage = int(a)
88 if o == '-q':
89 webchecker.verbose = 0
90 if o == '-v':
91 webchecker.verbose = webchecker.verbose + 1
92 if o == '-t':
93 extra_roots.append(a)
94 if o == '-a':
95 webchecker.nonames = not webchecker.nonames
96 root = Tk(className='Webchecker')
97 root.protocol("WM_DELETE_WINDOW", root.quit)
98 c = CheckerWindow(root)
99 c.setflags(verbose=webchecker.verbose, maxpage=webchecker.maxpage,
100 nonames=webchecker.nonames)
101 if args:
102 for arg in args[:-1]:
103 c.addroot(arg)
104 c.suggestroot(args[-1])
105 # Usually conditioned on whether external links
106 # will be checked, but since that's not a command
107 # line option, just toss them in.
108 for url_root in extra_roots:
109 # Make sure it's terminated by a slash,
110 # so that addroot doesn't discard the last
111 # directory component.
112 if url_root[-1] != "/":
113 url_root = url_root + "/"
114 c.addroot(url_root, add_to_do = 0)
115 root.mainloop()
116
117
118class CheckerWindow(webchecker.Checker):
119
120 def __init__(self, parent, root=webchecker.DEFROOT):
121 self.__parent = parent
122
123 self.__topcontrols = Frame(parent)
124 self.__topcontrols.pack(side=TOP, fill=X)
125 self.__label = Label(self.__topcontrols, text="Root URL:")
126 self.__label.pack(side=LEFT)
127 self.__rootentry = Entry(self.__topcontrols, width=60)
128 self.__rootentry.pack(side=LEFT)
129 self.__rootentry.bind('<Return>', self.enterroot)
130 self.__rootentry.focus_set()
131
132 self.__controls = Frame(parent)
133 self.__controls.pack(side=TOP, fill=X)
134 self.__running = 0
135 self.__start = Button(self.__controls, text="Run", command=self.start)
136 self.__start.pack(side=LEFT)
137 self.__stop = Button(self.__controls, text="Stop", command=self.stop,
138 state=DISABLED)
139 self.__stop.pack(side=LEFT)
140 self.__step = Button(self.__controls, text="Check one",
141 command=self.step)
142 self.__step.pack(side=LEFT)
143 self.__cv = BooleanVar(parent)
144 self.__cv.set(self.checkext)
145 self.__checkext = Checkbutton(self.__controls, variable=self.__cv,
146 command=self.update_checkext,
147 text="Check nonlocal links",)
148 self.__checkext.pack(side=LEFT)
149 self.__reset = Button(self.__controls, text="Start over", command=self.reset)
150 self.__reset.pack(side=LEFT)
151 if __name__ == '__main__': # No Quit button under Grail!
152 self.__quit = Button(self.__controls, text="Quit",
153 command=self.__parent.quit)
154 self.__quit.pack(side=RIGHT)
155
156 self.__status = Label(parent, text="Status: initial", anchor=W)
157 self.__status.pack(side=TOP, fill=X)
158 self.__checking = Label(parent, text="Idle", anchor=W)
159 self.__checking.pack(side=TOP, fill=X)
160 self.__mp = mp = MultiPanel(parent)
161 sys.stdout = self.__log = LogPanel(mp, "Log")
162 self.__todo = ListPanel(mp, "To check", self, self.showinfo)
163 self.__done = ListPanel(mp, "Checked", self, self.showinfo)
164 self.__bad = ListPanel(mp, "Bad links", self, self.showinfo)
165 self.__errors = ListPanel(mp, "Pages w/ bad links", self, self.showinfo)
166 self.__details = LogPanel(mp, "Details")
167 self.root_seed = None
168 webchecker.Checker.__init__(self)
169 if root:
170 root = str(root).strip()
171 if root:
172 self.suggestroot(root)
173 self.newstatus()
174
175 def reset(self):
176 webchecker.Checker.reset(self)
177 for p in self.__todo, self.__done, self.__bad, self.__errors:
178 p.clear()
179 if self.root_seed:
180 self.suggestroot(self.root_seed)
181
182 def suggestroot(self, root):
183 self.__rootentry.delete(0, END)
184 self.__rootentry.insert(END, root)
185 self.__rootentry.select_range(0, END)
186 self.root_seed = root
187
188 def enterroot(self, event=None):
189 root = self.__rootentry.get()
190 root = root.strip()
191 if root:
192 self.__checking.config(text="Adding root "+root)
193 self.__checking.update_idletasks()
194 self.addroot(root)
195 self.__checking.config(text="Idle")
196 try:
197 i = self.__todo.items.index(root)
198 except (ValueError, IndexError):
199 pass
200 else:
201 self.__todo.list.select_clear(0, END)
202 self.__todo.list.select_set(i)
203 self.__todo.list.yview(i)
204 self.__rootentry.delete(0, END)
205
206 def start(self):
207 self.__start.config(state=DISABLED, relief=SUNKEN)
208 self.__stop.config(state=NORMAL)
209 self.__step.config(state=DISABLED)
210 self.enterroot()
211 self.__running = 1
212 self.go()
213
214 def stop(self):
215 self.__stop.config(state=DISABLED, relief=SUNKEN)
216 self.__running = 0
217
218 def step(self):
219 self.__start.config(state=DISABLED)
220 self.__step.config(state=DISABLED, relief=SUNKEN)
221 self.enterroot()
222 self.__running = 0
223 self.dosomething()
224
225 def go(self):
226 if self.__running:
227 self.__parent.after_idle(self.dosomething)
228 else:
229 self.__checking.config(text="Idle")
230 self.__start.config(state=NORMAL, relief=RAISED)
231 self.__stop.config(state=DISABLED, relief=RAISED)
232 self.__step.config(state=NORMAL, relief=RAISED)
233
234 __busy = 0
235
236 def dosomething(self):
237 if self.__busy: return
238 self.__busy = 1
239 if self.todo:
240 l = self.__todo.selectedindices()
241 if l:
242 i = l[0]
243 else:
244 i = 0
245 self.__todo.list.select_set(i)
246 self.__todo.list.yview(i)
247 url = self.__todo.items[i]
248 self.__checking.config(text="Checking "+self.format_url(url))
249 self.__parent.update()
250 self.dopage(url)
251 else:
252 self.stop()
253 self.__busy = 0
254 self.go()
255
256 def showinfo(self, url):
257 d = self.__details
258 d.clear()
259 d.put("URL: %s\n" % self.format_url(url))
260 if self.bad.has_key(url):
261 d.put("Error: %s\n" % str(self.bad[url]))
262 if url in self.roots:
263 d.put("Note: This is a root URL\n")
264 if self.done.has_key(url):
265 d.put("Status: checked\n")
266 o = self.done[url]
267 elif self.todo.has_key(url):
268 d.put("Status: to check\n")
269 o = self.todo[url]
270 else:
271 d.put("Status: unknown (!)\n")
272 o = []
273 if (not url[1]) and self.errors.has_key(url[0]):
274 d.put("Bad links from this page:\n")
275 for triple in self.errors[url[0]]:
276 link, rawlink, msg = triple
277 d.put(" HREF %s" % self.format_url(link))
278 if self.format_url(link) != rawlink: d.put(" (%s)" %rawlink)
279 d.put("\n")
280 d.put(" error %s\n" % str(msg))
281 self.__mp.showpanel("Details")
282 for source, rawlink in o:
283 d.put("Origin: %s" % source)
284 if rawlink != self.format_url(url):
285 d.put(" (%s)" % rawlink)
286 d.put("\n")
287 d.text.yview("1.0")
288
289 def setbad(self, url, msg):
290 webchecker.Checker.setbad(self, url, msg)
291 self.__bad.insert(url)
292 self.newstatus()
293
294 def setgood(self, url):
295 webchecker.Checker.setgood(self, url)
296 self.__bad.remove(url)
297 self.newstatus()
298
299 def newlink(self, url, origin):
300 webchecker.Checker.newlink(self, url, origin)
301 if self.done.has_key(url):
302 self.__done.insert(url)
303 elif self.todo.has_key(url):
304 self.__todo.insert(url)
305 self.newstatus()
306
307 def markdone(self, url):
308 webchecker.Checker.markdone(self, url)
309 self.__done.insert(url)
310 self.__todo.remove(url)
311 self.newstatus()
312
313 def seterror(self, url, triple):
314 webchecker.Checker.seterror(self, url, triple)
315 self.__errors.insert((url, ''))
316 self.newstatus()
317
318 def newstatus(self):
319 self.__status.config(text="Status: "+self.status())
320 self.__parent.update()
321
322 def update_checkext(self):
323 self.checkext = self.__cv.get()
324
325
326class ListPanel:
327
328 def __init__(self, mp, name, checker, showinfo=None):
329 self.mp = mp
330 self.name = name
331 self.showinfo = showinfo
332 self.checker = checker
333 self.panel = mp.addpanel(name)
334 self.list, self.frame = tktools.make_list_box(
335 self.panel, width=60, height=5)
336 self.list.config(exportselection=0)
337 if showinfo:
338 self.list.bind('<Double-Button-1>', self.doubleclick)
339 self.items = []
340
341 def clear(self):
342 self.items = []
343 self.list.delete(0, END)
344 self.mp.hidepanel(self.name)
345
346 def doubleclick(self, event):
347 l = self.selectedindices()
348 if l:
349 self.showinfo(self.items[l[0]])
350
351 def selectedindices(self):
352 l = self.list.curselection()
353 if not l: return []
354 return map(int, l)
355
356 def insert(self, url):
357 if url not in self.items:
358 if not self.items:
359 self.mp.showpanel(self.name)
360 # (I tried sorting alphabetically, but the display is too jumpy)
361 i = len(self.items)
362 self.list.insert(i, self.checker.format_url(url))
363 self.list.yview(i)
364 self.items.insert(i, url)
365
366 def remove(self, url):
367 try:
368 i = self.items.index(url)
369 except (ValueError, IndexError):
370 pass
371 else:
372 was_selected = i in self.selectedindices()
373 self.list.delete(i)
374 del self.items[i]
375 if not self.items:
376 self.mp.hidepanel(self.name)
377 elif was_selected:
378 if i >= len(self.items):
379 i = len(self.items) - 1
380 self.list.select_set(i)
381
382
383class LogPanel:
384
385 def __init__(self, mp, name):
386 self.mp = mp
387 self.name = name
388 self.panel = mp.addpanel(name)
389 self.text, self.frame = tktools.make_text_box(self.panel, height=10)
390 self.text.config(wrap=NONE)
391
392 def clear(self):
393 self.text.delete("1.0", END)
394 self.text.yview("1.0")
395
396 def put(self, s):
397 self.text.insert(END, s)
398 if '\n' in s:
399 self.text.yview(END)
400
401 def write(self, s):
402 self.text.insert(END, s)
403 if '\n' in s:
404 self.text.yview(END)
405 self.panel.update()
406
407
408class MultiPanel:
409
410 def __init__(self, parent):
411 self.parent = parent
412 self.frame = Frame(self.parent)
413 self.frame.pack(expand=1, fill=BOTH)
414 self.topframe = Frame(self.frame, borderwidth=2, relief=RAISED)
415 self.topframe.pack(fill=X)
416 self.botframe = Frame(self.frame)
417 self.botframe.pack(expand=1, fill=BOTH)
418 self.panelnames = []
419 self.panels = {}
420
421 def addpanel(self, name, on=0):
422 v = StringVar(self.parent)
423 if on:
424 v.set(name)
425 else:
426 v.set("")
427 check = Checkbutton(self.topframe, text=name,
428 offvalue="", onvalue=name, variable=v,
429 command=self.checkpanel)
430 check.pack(side=LEFT)
431 panel = Frame(self.botframe)
432 label = Label(panel, text=name, borderwidth=2, relief=RAISED, anchor=W)
433 label.pack(side=TOP, fill=X)
434 t = v, check, panel
435 self.panelnames.append(name)
436 self.panels[name] = t
437 if on:
438 panel.pack(expand=1, fill=BOTH)
439 return panel
440
441 def showpanel(self, name):
442 v, check, panel = self.panels[name]
443 v.set(name)
444 panel.pack(expand=1, fill=BOTH)
445
446 def hidepanel(self, name):
447 v, check, panel = self.panels[name]
448 v.set("")
449 panel.pack_forget()
450
451 def checkpanel(self):
452 for name in self.panelnames:
453 v, check, panel = self.panels[name]
454 panel.pack_forget()
455 for name in self.panelnames:
456 v, check, panel = self.panels[name]
457 if v.get():
458 panel.pack(expand=1, fill=BOTH)
459
460
461if __name__ == '__main__':
462 main()
Note: See TracBrowser for help on using the repository browser.