move sections
[python/dscho.git] / Tools / webchecker / wcgui.py
blobcd5326379d6b1c46a129f3d7e077ef818fa33319
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
42 -t root -- specify root dir which should be treated as internal (can repeat)
43 -a -- don't check name anchors
45 Command line arguments:
47 rooturl -- URL to start checking
48 (default %(DEFROOT)s)
50 XXX The command line options (-m, -q, -v) should be GUI accessible.
52 XXX The roots should be visible as a list (?).
54 XXX The multipanel user interface is clumsy.
56 """
58 # ' Emacs bait
61 import sys
62 import getopt
63 from Tkinter import *
64 import tktools
65 import webchecker
67 def main():
68 try:
69 opts, args = getopt.getopt(sys.argv[1:], 't:m:qva')
70 except getopt.error, msg:
71 sys.stdout = sys.stderr
72 print msg
73 print __doc__%vars(webchecker)
74 sys.exit(2)
75 webchecker.verbose = webchecker.VERBOSE
76 webchecker.nonames = webchecker.NONAMES
77 webchecker.maxpage = webchecker.MAXPAGE
78 extra_roots = []
79 for o, a in opts:
80 if o == '-m':
81 webchecker.maxpage = int(a)
82 if o == '-q':
83 webchecker.verbose = 0
84 if o == '-v':
85 webchecker.verbose = webchecker.verbose + 1
86 if o == '-t':
87 extra_roots.append(a)
88 if o == '-a':
89 webchecker.nonames = not webchecker.nonames
90 root = Tk(className='Webchecker')
91 root.protocol("WM_DELETE_WINDOW", root.quit)
92 c = CheckerWindow(root)
93 c.setflags(verbose=webchecker.verbose, maxpage=webchecker.maxpage,
94 nonames=webchecker.nonames)
95 if args:
96 for arg in args[:-1]:
97 c.addroot(arg)
98 c.suggestroot(args[-1])
99 # Usually conditioned on whether external links
100 # will be checked, but since that's not a command
101 # line option, just toss them in.
102 for url_root in extra_roots:
103 # Make sure it's terminated by a slash,
104 # so that addroot doesn't discard the last
105 # directory component.
106 if url_root[-1] != "/":
107 url_root = url_root + "/"
108 c.addroot(url_root, add_to_do = 0)
109 root.mainloop()
112 class CheckerWindow(webchecker.Checker):
114 def __init__(self, parent, root=webchecker.DEFROOT):
115 self.__parent = parent
117 self.__topcontrols = Frame(parent)
118 self.__topcontrols.pack(side=TOP, fill=X)
119 self.__label = Label(self.__topcontrols, text="Root URL:")
120 self.__label.pack(side=LEFT)
121 self.__rootentry = Entry(self.__topcontrols, width=60)
122 self.__rootentry.pack(side=LEFT)
123 self.__rootentry.bind('<Return>', self.enterroot)
124 self.__rootentry.focus_set()
126 self.__controls = Frame(parent)
127 self.__controls.pack(side=TOP, fill=X)
128 self.__running = 0
129 self.__start = Button(self.__controls, text="Run", command=self.start)
130 self.__start.pack(side=LEFT)
131 self.__stop = Button(self.__controls, text="Stop", command=self.stop,
132 state=DISABLED)
133 self.__stop.pack(side=LEFT)
134 self.__step = Button(self.__controls, text="Check one",
135 command=self.step)
136 self.__step.pack(side=LEFT)
137 self.__cv = BooleanVar(parent)
138 self.__cv.set(self.checkext)
139 self.__checkext = Checkbutton(self.__controls, variable=self.__cv,
140 command=self.update_checkext,
141 text="Check nonlocal links",)
142 self.__checkext.pack(side=LEFT)
143 self.__reset = Button(self.__controls, text="Start over", command=self.reset)
144 self.__reset.pack(side=LEFT)
145 if __name__ == '__main__': # No Quit button under Grail!
146 self.__quit = Button(self.__controls, text="Quit",
147 command=self.__parent.quit)
148 self.__quit.pack(side=RIGHT)
150 self.__status = Label(parent, text="Status: initial", anchor=W)
151 self.__status.pack(side=TOP, fill=X)
152 self.__checking = Label(parent, text="Idle", anchor=W)
153 self.__checking.pack(side=TOP, fill=X)
154 self.__mp = mp = MultiPanel(parent)
155 sys.stdout = self.__log = LogPanel(mp, "Log")
156 self.__todo = ListPanel(mp, "To check", self, self.showinfo)
157 self.__done = ListPanel(mp, "Checked", self, self.showinfo)
158 self.__bad = ListPanel(mp, "Bad links", self, self.showinfo)
159 self.__errors = ListPanel(mp, "Pages w/ bad links", self, self.showinfo)
160 self.__details = LogPanel(mp, "Details")
161 self.root_seed = None
162 webchecker.Checker.__init__(self)
163 if root:
164 root = str(root).strip()
165 if root:
166 self.suggestroot(root)
167 self.newstatus()
169 def reset(self):
170 webchecker.Checker.reset(self)
171 for p in self.__todo, self.__done, self.__bad, self.__errors:
172 p.clear()
173 if self.root_seed:
174 self.suggestroot(self.root_seed)
176 def suggestroot(self, root):
177 self.__rootentry.delete(0, END)
178 self.__rootentry.insert(END, root)
179 self.__rootentry.select_range(0, END)
180 self.root_seed = root
182 def enterroot(self, event=None):
183 root = self.__rootentry.get()
184 root = root.strip()
185 if root:
186 self.__checking.config(text="Adding root "+root)
187 self.__checking.update_idletasks()
188 self.addroot(root)
189 self.__checking.config(text="Idle")
190 try:
191 i = self.__todo.items.index(root)
192 except (ValueError, IndexError):
193 pass
194 else:
195 self.__todo.list.select_clear(0, END)
196 self.__todo.list.select_set(i)
197 self.__todo.list.yview(i)
198 self.__rootentry.delete(0, END)
200 def start(self):
201 self.__start.config(state=DISABLED, relief=SUNKEN)
202 self.__stop.config(state=NORMAL)
203 self.__step.config(state=DISABLED)
204 self.enterroot()
205 self.__running = 1
206 self.go()
208 def stop(self):
209 self.__stop.config(state=DISABLED, relief=SUNKEN)
210 self.__running = 0
212 def step(self):
213 self.__start.config(state=DISABLED)
214 self.__step.config(state=DISABLED, relief=SUNKEN)
215 self.enterroot()
216 self.__running = 0
217 self.dosomething()
219 def go(self):
220 if self.__running:
221 self.__parent.after_idle(self.dosomething)
222 else:
223 self.__checking.config(text="Idle")
224 self.__start.config(state=NORMAL, relief=RAISED)
225 self.__stop.config(state=DISABLED, relief=RAISED)
226 self.__step.config(state=NORMAL, relief=RAISED)
228 __busy = 0
230 def dosomething(self):
231 if self.__busy: return
232 self.__busy = 1
233 if self.todo:
234 l = self.__todo.selectedindices()
235 if l:
236 i = l[0]
237 else:
238 i = 0
239 self.__todo.list.select_set(i)
240 self.__todo.list.yview(i)
241 url = self.__todo.items[i]
242 self.__checking.config(text="Checking "+self.format_url(url))
243 self.__parent.update()
244 self.dopage(url)
245 else:
246 self.stop()
247 self.__busy = 0
248 self.go()
250 def showinfo(self, url):
251 d = self.__details
252 d.clear()
253 d.put("URL: %s\n" % self.format_url(url))
254 if self.bad.has_key(url):
255 d.put("Error: %s\n" % str(self.bad[url]))
256 if url in self.roots:
257 d.put("Note: This is a root URL\n")
258 if self.done.has_key(url):
259 d.put("Status: checked\n")
260 o = self.done[url]
261 elif self.todo.has_key(url):
262 d.put("Status: to check\n")
263 o = self.todo[url]
264 else:
265 d.put("Status: unknown (!)\n")
266 o = []
267 if (not url[1]) and self.errors.has_key(url[0]):
268 d.put("Bad links from this page:\n")
269 for triple in self.errors[url[0]]:
270 link, rawlink, msg = triple
271 d.put(" HREF %s" % self.format_url(link))
272 if self.format_url(link) != rawlink: d.put(" (%s)" %rawlink)
273 d.put("\n")
274 d.put(" error %s\n" % str(msg))
275 self.__mp.showpanel("Details")
276 for source, rawlink in o:
277 d.put("Origin: %s" % source)
278 if rawlink != self.format_url(url):
279 d.put(" (%s)" % rawlink)
280 d.put("\n")
281 d.text.yview("1.0")
283 def setbad(self, url, msg):
284 webchecker.Checker.setbad(self, url, msg)
285 self.__bad.insert(url)
286 self.newstatus()
288 def setgood(self, url):
289 webchecker.Checker.setgood(self, url)
290 self.__bad.remove(url)
291 self.newstatus()
293 def newlink(self, url, origin):
294 webchecker.Checker.newlink(self, url, origin)
295 if self.done.has_key(url):
296 self.__done.insert(url)
297 elif self.todo.has_key(url):
298 self.__todo.insert(url)
299 self.newstatus()
301 def markdone(self, url):
302 webchecker.Checker.markdone(self, url)
303 self.__done.insert(url)
304 self.__todo.remove(url)
305 self.newstatus()
307 def seterror(self, url, triple):
308 webchecker.Checker.seterror(self, url, triple)
309 self.__errors.insert((url, ''))
310 self.newstatus()
312 def newstatus(self):
313 self.__status.config(text="Status: "+self.status())
314 self.__parent.update()
316 def update_checkext(self):
317 self.checkext = self.__cv.get()
320 class ListPanel:
322 def __init__(self, mp, name, checker, showinfo=None):
323 self.mp = mp
324 self.name = name
325 self.showinfo = showinfo
326 self.checker = checker
327 self.panel = mp.addpanel(name)
328 self.list, self.frame = tktools.make_list_box(
329 self.panel, width=60, height=5)
330 self.list.config(exportselection=0)
331 if showinfo:
332 self.list.bind('<Double-Button-1>', self.doubleclick)
333 self.items = []
335 def clear(self):
336 self.items = []
337 self.list.delete(0, END)
338 self.mp.hidepanel(self.name)
340 def doubleclick(self, event):
341 l = self.selectedindices()
342 if l:
343 self.showinfo(self.items[l[0]])
345 def selectedindices(self):
346 l = self.list.curselection()
347 if not l: return []
348 return map(int, l)
350 def insert(self, url):
351 if url not in self.items:
352 if not self.items:
353 self.mp.showpanel(self.name)
354 # (I tried sorting alphabetically, but the display is too jumpy)
355 i = len(self.items)
356 self.list.insert(i, self.checker.format_url(url))
357 self.list.yview(i)
358 self.items.insert(i, url)
360 def remove(self, url):
361 try:
362 i = self.items.index(url)
363 except (ValueError, IndexError):
364 pass
365 else:
366 was_selected = i in self.selectedindices()
367 self.list.delete(i)
368 del self.items[i]
369 if not self.items:
370 self.mp.hidepanel(self.name)
371 elif was_selected:
372 if i >= len(self.items):
373 i = len(self.items) - 1
374 self.list.select_set(i)
377 class LogPanel:
379 def __init__(self, mp, name):
380 self.mp = mp
381 self.name = name
382 self.panel = mp.addpanel(name)
383 self.text, self.frame = tktools.make_text_box(self.panel, height=10)
384 self.text.config(wrap=NONE)
386 def clear(self):
387 self.text.delete("1.0", END)
388 self.text.yview("1.0")
390 def put(self, s):
391 self.text.insert(END, s)
392 if '\n' in s:
393 self.text.yview(END)
395 def write(self, s):
396 self.text.insert(END, s)
397 if '\n' in s:
398 self.text.yview(END)
399 self.panel.update()
402 class MultiPanel:
404 def __init__(self, parent):
405 self.parent = parent
406 self.frame = Frame(self.parent)
407 self.frame.pack(expand=1, fill=BOTH)
408 self.topframe = Frame(self.frame, borderwidth=2, relief=RAISED)
409 self.topframe.pack(fill=X)
410 self.botframe = Frame(self.frame)
411 self.botframe.pack(expand=1, fill=BOTH)
412 self.panelnames = []
413 self.panels = {}
415 def addpanel(self, name, on=0):
416 v = StringVar(self.parent)
417 if on:
418 v.set(name)
419 else:
420 v.set("")
421 check = Checkbutton(self.topframe, text=name,
422 offvalue="", onvalue=name, variable=v,
423 command=self.checkpanel)
424 check.pack(side=LEFT)
425 panel = Frame(self.botframe)
426 label = Label(panel, text=name, borderwidth=2, relief=RAISED, anchor=W)
427 label.pack(side=TOP, fill=X)
428 t = v, check, panel
429 self.panelnames.append(name)
430 self.panels[name] = t
431 if on:
432 panel.pack(expand=1, fill=BOTH)
433 return panel
435 def showpanel(self, name):
436 v, check, panel = self.panels[name]
437 v.set(name)
438 panel.pack(expand=1, fill=BOTH)
440 def hidepanel(self, name):
441 v, check, panel = self.panels[name]
442 v.set("")
443 panel.pack_forget()
445 def checkpanel(self):
446 for name in self.panelnames:
447 v, check, panel = self.panels[name]
448 panel.pack_forget()
449 for name in self.panelnames:
450 v, check, panel = self.panels[name]
451 if v.get():
452 panel.pack(expand=1, fill=BOTH)
455 if __name__ == '__main__':
456 main()