blob: 027718fb5bf2c266f3308bf18891a53d647efee3 [file] [log] [blame]
Guido van Rossum06981c31997-01-31 18:58:12 +00001#! /usr/bin/env python
2
3"""GUI interface to webchecker.
4
5This works as a Grail applet too! E.g.
6
Guido van Rossum4f6ecda1997-02-01 05:17:29 +00007 <APPLET CODE=wcgui.py NAME=CheckerWindow></APPLET>
Guido van Rossum06981c31997-01-31 18:58:12 +00008
Guido van Rossumaf310c11997-02-02 23:30:32 +00009Checkpoints are not (yet??? ever???) supported.
Guido van Rossum06981c31997-01-31 18:58:12 +000010
11User interface:
12
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000013Enter a root to check in the text entry box. To enter more than one root,
Guido van Rossumaf310c11997-02-02 23:30:32 +000014enter them one at a time and press <Return> for each one.
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000015
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
Guido van Rossumaf310c11997-02-02 23:30:32 +000018entry 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.
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000021
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:
Guido van Rossum06981c31997-01-31 18:58:12 +000025
26Log -- raw output from the checker (-v, -q affect this)
Guido van Rossumaf310c11997-02-02 23:30:32 +000027To check -- links discovered but not yet checked
28Checked -- links that have been checked
Guido van Rossum06981c31997-01-31 18:58:12 +000029Bad links -- links that failed upon checking
Guido van Rossumaf310c11997-02-02 23:30:32 +000030Errors -- pages containing at least one bad link
Guido van Rossum06981c31997-01-31 18:58:12 +000031Details -- details about one URL; double click on a URL in any of
Guido van Rossumaf310c11997-02-02 23:30:32 +000032 the above list panels (not in Log) will show details
33 for that URL
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000034
Guido van Rossum06981c31997-01-31 18:58:12 +000035Use 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)
Guido van Rossum06981c31997-01-31 18:58:12 +000040-q -- quiet operation (also suppresses external links report)
41-v -- verbose operation; repeating -v will increase verbosity
Guido van Rossum06981c31997-01-31 18:58:12 +000042
43Command line arguments:
44
45rooturl -- URL to start checking
46 (default %(DEFROOT)s)
47
Guido van Rossumaf310c11997-02-02 23:30:32 +000048XXX The command line options (-m, -q, -v) should be GUI accessible.
Guido van Rossum06981c31997-01-31 18:58:12 +000049
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000050XXX The roots should be visible as a list (?).
Guido van Rossum06981c31997-01-31 18:58:12 +000051
Guido van Rossumaf310c11997-02-02 23:30:32 +000052XXX The multipanel user interface is clumsy.
Guido van Rossum06981c31997-01-31 18:58:12 +000053
54"""
55
56# ' Emacs bait
57
58
59import sys
60import getopt
61import string
62from Tkinter import *
63import tktools
64import webchecker
65import random
66
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000067# 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
Guido van Rossum06981c31997-01-31 18:58:12 +000072
73def main():
Guido van Rossum06981c31997-01-31 18:58:12 +000074 try:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000075 opts, args = getopt.getopt(sys.argv[1:], 'm:qv')
Guido van Rossum06981c31997-01-31 18:58:12 +000076 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
Guido van Rossum06981c31997-01-31 18:58:12 +000088 root = Tk(className='Webchecker')
89 root.protocol("WM_DELETE_WINDOW", root.quit)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000090 c = CheckerWindow(root)
91 if args:
92 for arg in args[:-1]:
93 c.addroot(arg)
94 c.suggestroot(args[-1])
Guido van Rossum06981c31997-01-31 18:58:12 +000095 root.mainloop()
96
97
98class CheckerWindow(webchecker.Checker):
99
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000100 def __init__(self, parent, root=webchecker.DEFROOT):
Guido van Rossum06981c31997-01-31 18:58:12 +0000101 self.__parent = parent
Guido van Rossumaf310c11997-02-02 23:30:32 +0000102
103 self.__topcontrols = Frame(parent)
104 self.__topcontrols.pack(side=TOP, fill=X)
105 self.__label = Label(self.__topcontrols, text="Root URL:")
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000106 self.__label.pack(side=LEFT)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000107 self.__rootentry = Entry(self.__topcontrols, width=60)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000108 self.__rootentry.pack(side=LEFT)
109 self.__rootentry.bind('<Return>', self.enterroot)
110 self.__rootentry.focus_set()
Guido van Rossumaf310c11997-02-02 23:30:32 +0000111
112 self.__controls = Frame(parent)
113 self.__controls.pack(side=TOP, fill=X)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000114 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)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000120 self.__step = Button(self.__controls, text="Check one",
121 command=self.step)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000122 self.__step.pack(side=LEFT)
Guido van Rossum88b02cf1998-03-05 20:12:18 +0000123 self.__cv = BooleanVar(parent)
124 self.__cv.set(self.checkext)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000125 self.__checkext = Checkbutton(self.__controls, variable=self.__cv,
Guido van Rossum88b02cf1998-03-05 20:12:18 +0000126 command=self.update_checkext,
127 text="Check nonlocal links",)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000128 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)
135
Guido van Rossum06981c31997-01-31 18:58:12 +0000136 self.__status = Label(parent, text="Status: initial", anchor=W)
137 self.__status.pack(side=TOP, fill=X)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000138 self.__checking = Label(parent, text="Idle", anchor=W)
Guido van Rossum06981c31997-01-31 18:58:12 +0000139 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)
Guido van Rossum06981c31997-01-31 18:58:12 +0000143 self.__done = ListPanel(mp, "Checked", self.showinfo)
144 self.__bad = ListPanel(mp, "Bad links", self.showinfo)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000145 self.__errors = ListPanel(mp, "Pages w/ bad links", self.showinfo)
Guido van Rossum06981c31997-01-31 18:58:12 +0000146 self.__details = LogPanel(mp, "Details")
Guido van Rossum06981c31997-01-31 18:58:12 +0000147 webchecker.Checker.__init__(self)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000148 if root:
149 root = string.strip(str(root))
150 if root:
151 self.suggestroot(root)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000152 self.newstatus()
153
Guido van Rossumaf310c11997-02-02 23:30:32 +0000154 def reset(self):
155 webchecker.Checker.reset(self)
156 for p in self.__todo, self.__done, self.__bad, self.__errors:
157 p.clear()
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000158
159 def suggestroot(self, root):
160 self.__rootentry.delete(0, END)
161 self.__rootentry.insert(END, root)
162 self.__rootentry.select_range(0, END)
163
164 def enterroot(self, event=None):
165 root = self.__rootentry.get()
166 root = string.strip(root)
167 if root:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000168 self.__checking.config(text="Adding root "+root)
169 self.__checking.update_idletasks()
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000170 self.addroot(root)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000171 self.__checking.config(text="Idle")
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000172 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)
181
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()
189
190 def stop(self):
Guido van Rossumaf310c11997-02-02 23:30:32 +0000191 self.__stop.config(state=DISABLED, relief=SUNKEN)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000192 self.__running = 0
193
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()
Guido van Rossum06981c31997-01-31 18:58:12 +0000200
201 def go(self):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000202 if self.__running:
Guido van Rossum06981c31997-01-31 18:58:12 +0000203 self.__parent.after_idle(self.dosomething)
204 else:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000205 self.__checking.config(text="Idle")
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000206 self.__start.config(state=NORMAL, relief=RAISED)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000207 self.__stop.config(state=DISABLED, relief=RAISED)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000208 self.__step.config(state=NORMAL, relief=RAISED)
209
210 __busy = 0
Guido van Rossum06981c31997-01-31 18:58:12 +0000211
212 def dosomething(self):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000213 if self.__busy: return
214 self.__busy = 1
Guido van Rossum06981c31997-01-31 18:58:12 +0000215 if self.todo:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000216 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)
Guido van Rossum06981c31997-01-31 18:58:12 +0000223 url = self.__todo.items[i]
Guido van Rossumaf310c11997-02-02 23:30:32 +0000224 self.__checking.config(text="Checking "+url)
Guido van Rossum06981c31997-01-31 18:58:12 +0000225 self.__parent.update()
226 self.dopage(url)
Guido van Rossum06981c31997-01-31 18:58:12 +0000227 else:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000228 self.stop()
229 self.__busy = 0
Guido van Rossum06981c31997-01-31 18:58:12 +0000230 self.go()
231
232 def showinfo(self, url):
233 d = self.__details
234 d.clear()
Guido van Rossumaf310c11997-02-02 23:30:32 +0000235 d.put("URL: %s\n" % url)
Guido van Rossum06981c31997-01-31 18:58:12 +0000236 if self.bad.has_key(url):
Guido van Rossumaf310c11997-02-02 23:30:32 +0000237 d.put("Error: %s\n" % str(self.bad[url]))
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000238 if url in self.roots:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000239 d.put("Note: This is a root URL\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000240 if self.done.has_key(url):
Guido van Rossumaf310c11997-02-02 23:30:32 +0000241 d.put("Status: checked\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000242 o = self.done[url]
243 elif self.todo.has_key(url):
Guido van Rossumaf310c11997-02-02 23:30:32 +0000244 d.put("Status: to check\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000245 o = self.todo[url]
Guido van Rossum06981c31997-01-31 18:58:12 +0000246 else:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000247 d.put("Status: unknown (!)\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000248 o = []
Guido van Rossumaf310c11997-02-02 23:30:32 +0000249 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))
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000257 self.__mp.showpanel("Details")
Guido van Rossum06981c31997-01-31 18:58:12 +0000258 for source, rawlink in o:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000259 d.put("Origin: %s" % source)
Guido van Rossum06981c31997-01-31 18:58:12 +0000260 if rawlink != url:
Guido van Rossumaf310c11997-02-02 23:30:32 +0000261 d.put(" (%s)" % rawlink)
262 d.put("\n")
Guido van Rossum29f65331997-05-09 03:19:29 +0000263 d.text.yview("1.0")
Guido van Rossum06981c31997-01-31 18:58:12 +0000264
265 def setbad(self, url, msg):
266 webchecker.Checker.setbad(self, url, msg)
267 self.__bad.insert(url)
268 self.newstatus()
269
270 def setgood(self, url):
271 webchecker.Checker.setgood(self, url)
272 self.__bad.remove(url)
273 self.newstatus()
274
Guido van Rossumaf310c11997-02-02 23:30:32 +0000275 def newlink(self, url, origin):
276 webchecker.Checker.newlink(self, url, origin)
Guido van Rossum06981c31997-01-31 18:58:12 +0000277 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()
282
283 def markdone(self, url):
284 webchecker.Checker.markdone(self, url)
285 self.__done.insert(url)
286 self.__todo.remove(url)
287 self.newstatus()
288
Guido van Rossumaf310c11997-02-02 23:30:32 +0000289 def seterror(self, url, triple):
290 webchecker.Checker.seterror(self, url, triple)
291 self.__errors.insert(url)
292 self.newstatus()
293
294 def newstatus(self):
295 self.__status.config(text="Status: "+self.status())
296 self.__parent.update()
297
Guido van Rossum88b02cf1998-03-05 20:12:18 +0000298 def update_checkext(self):
299 self.checkext = self.__cv.get()
300
Guido van Rossum06981c31997-01-31 18:58:12 +0000301
302class ListPanel:
303
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:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000313 self.list.bind('<Double-Button-1>', self.doubleclick)
Guido van Rossum06981c31997-01-31 18:58:12 +0000314 self.items = []
315
Guido van Rossumaf310c11997-02-02 23:30:32 +0000316 def clear(self):
317 self.items = []
318 self.list.delete(0, END)
319 self.mp.hidepanel(self.name)
320
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000321 def doubleclick(self, event):
322 l = self.selectedindices()
323 if l:
324 self.showinfo(self.list.get(l[0]))
325
326 def selectedindices(self):
327 l = self.list.curselection()
328 if not l: return []
329 return map(string.atoi, l)
Guido van Rossum06981c31997-01-31 18:58:12 +0000330
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)
Guido van Rossumaf310c11997-02-02 23:30:32 +0000338 self.list.yview(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000339 self.items.insert(i, url)
340
341 def remove(self, url):
342 try:
343 i = self.items.index(url)
344 except (ValueError, IndexError):
345 pass
346 else:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000347 was_selected = i in self.selectedindices()
Guido van Rossum06981c31997-01-31 18:58:12 +0000348 self.list.delete(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000349 del self.items[i]
350 if not self.items:
351 self.mp.hidepanel(self.name)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000352 elif was_selected:
353 if i >= len(self.items):
354 i = len(self.items) - 1
355 self.list.select_set(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000356
357
358class LogPanel:
359
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)
366
367 def clear(self):
368 self.text.delete("1.0", END)
369 self.text.yview("1.0")
370
Guido van Rossumaf310c11997-02-02 23:30:32 +0000371 def put(self, s):
372 self.text.insert(END, s)
373 if '\n' in s:
374 self.text.yview(END)
375
Guido van Rossum06981c31997-01-31 18:58:12 +0000376 def write(self, s):
377 self.text.insert(END, s)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000378 if '\n' in s:
379 self.text.yview(END)
380 self.panel.update()
Guido van Rossum06981c31997-01-31 18:58:12 +0000381
382
383class MultiPanel:
384
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 = {}
395
396 def addpanel(self, name, on=0):
Guido van Rossum88b02cf1998-03-05 20:12:18 +0000397 v = StringVar(self.parent)
Guido van Rossum06981c31997-01-31 18:58:12 +0000398 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
415
416 def showpanel(self, name):
417 v, check, panel = self.panels[name]
418 v.set(name)
419 panel.pack(expand=1, fill=BOTH)
420
421 def hidepanel(self, name):
422 v, check, panel = self.panels[name]
423 v.set("")
424 panel.pack_forget()
425
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)
434
435
436if __name__ == '__main__':
437 main()