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