blob: f5649be1ab1e5dfdabbaf064c43edaf8bd908849 [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
9Checkpoints are not (yet?) supported.
10
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,
14enter each root and press <Return>.
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.
19
20A series of checkbuttons determines whether the corresponding output panel
21is shown. List panels are also automatically shown or hidden when their
22status changes between empty to non-empty. There are six panels:
Guido van Rossum06981c31997-01-31 18:58:12 +000023
24Log -- raw output from the checker (-v, -q affect this)
25To check -- local links discovered but not yet checked
26Off site -- links discovered that point off site
27Checked -- local links that have been checked
28Bad links -- links that failed upon checking
29Details -- details about one URL; double click on a URL in any of
30 the aboce list panels (not in Log) will show that URL
31
32XXX There ought to be a list of pages known to contain at least one
33bad link.
34
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000035XXX The checking of off site links should be made more similar to the
36checking of local links (even if they are checked after all local links are
37checked).
38
Guido van Rossum06981c31997-01-31 18:58:12 +000039Use your window manager's Close command to quit.
40
41Command line options:
42
43-m bytes -- skip HTML pages larger than this size (default %(MAXPAGE)d)
44-n -- reports only, no checking (use with -R)
45-q -- quiet operation (also suppresses external links report)
46-v -- verbose operation; repeating -v will increase verbosity
Guido van Rossum06981c31997-01-31 18:58:12 +000047
48Command line arguments:
49
50rooturl -- URL to start checking
51 (default %(DEFROOT)s)
52
53XXX The command line options should all be GUI accessible.
54
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000055XXX The roots should be visible as a list (?).
Guido van Rossum06981c31997-01-31 18:58:12 +000056
57XXX The multipanel user interface is bogus.
58
59"""
60
61# ' Emacs bait
62
63
64import sys
65import getopt
66import string
67from Tkinter import *
68import tktools
69import webchecker
70import random
71
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000072# Override some for a weaker platform
73if sys.platform == 'mac':
74 webchecker.DEFROOT = "http://grail.cnri.reston.va.us/"
75 webchecker.MAXPAGE = 50000
76 webchecker.verbose = 4
Guido van Rossum06981c31997-01-31 18:58:12 +000077
78def main():
Guido van Rossum06981c31997-01-31 18:58:12 +000079 try:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000080 opts, args = getopt.getopt(sys.argv[1:], 'm:qv')
Guido van Rossum06981c31997-01-31 18:58:12 +000081 except getopt.error, msg:
82 sys.stdout = sys.stderr
83 print msg
84 print __doc__%vars(webchecker)
85 sys.exit(2)
86 for o, a in opts:
87 if o == '-m':
88 webchecker.maxpage = string.atoi(a)
89 if o == '-q':
90 webchecker.verbose = 0
91 if o == '-v':
92 webchecker.verbose = webchecker.verbose + 1
Guido van Rossum06981c31997-01-31 18:58:12 +000093 root = Tk(className='Webchecker')
94 root.protocol("WM_DELETE_WINDOW", root.quit)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +000095 c = CheckerWindow(root)
96 if args:
97 for arg in args[:-1]:
98 c.addroot(arg)
99 c.suggestroot(args[-1])
Guido van Rossum06981c31997-01-31 18:58:12 +0000100 root.mainloop()
101
102
103class CheckerWindow(webchecker.Checker):
104
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000105 def __init__(self, parent, root=webchecker.DEFROOT):
Guido van Rossum06981c31997-01-31 18:58:12 +0000106 self.__parent = parent
Guido van Rossum06981c31997-01-31 18:58:12 +0000107 self.__controls = Frame(parent)
108 self.__controls.pack(side=TOP, fill=X)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000109 self.__label = Label(self.__controls, text="Root URL:")
110 self.__label.pack(side=LEFT)
111 self.__rootentry = Entry(self.__controls, width=60)
112 self.__rootentry.pack(side=LEFT)
113 self.__rootentry.bind('<Return>', self.enterroot)
114 self.__rootentry.focus_set()
115 self.__running = 0
116 self.__start = Button(self.__controls, text="Run", command=self.start)
117 self.__start.pack(side=LEFT)
118 self.__stop = Button(self.__controls, text="Stop", command=self.stop,
119 state=DISABLED)
120 self.__stop.pack(side=LEFT)
121 self.__step = Button(self.__controls, text="Check one", command=self.step)
122 self.__step.pack(side=LEFT)
Guido van Rossum06981c31997-01-31 18:58:12 +0000123 self.__status = Label(parent, text="Status: initial", anchor=W)
124 self.__status.pack(side=TOP, fill=X)
125 self.__checking = Label(parent, text="Checking: none", anchor=W)
126 self.__checking.pack(side=TOP, fill=X)
127 self.__mp = mp = MultiPanel(parent)
128 sys.stdout = self.__log = LogPanel(mp, "Log")
129 self.__todo = ListPanel(mp, "To check", self.showinfo)
130 self.__ext = ListPanel(mp, "Off site", self.showinfo)
131 self.__done = ListPanel(mp, "Checked", self.showinfo)
132 self.__bad = ListPanel(mp, "Bad links", self.showinfo)
133 self.__details = LogPanel(mp, "Details")
134 self.__extodo = []
135 webchecker.Checker.__init__(self)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000136 if root:
137 root = string.strip(str(root))
138 if root:
139 self.suggestroot(root)
140
141 def suggestroot(self, root):
142 self.__rootentry.delete(0, END)
143 self.__rootentry.insert(END, root)
144 self.__rootentry.select_range(0, END)
145
146 def enterroot(self, event=None):
147 root = self.__rootentry.get()
148 root = string.strip(root)
149 if root:
150 self.addroot(root)
151 try:
152 i = self.__todo.items.index(root)
153 except (ValueError, IndexError):
154 pass
155 else:
156 self.__todo.list.select_clear(0, END)
157 self.__todo.list.select_set(i)
158 self.__todo.list.yview(i)
159 self.__rootentry.delete(0, END)
160
161 def start(self):
162 self.__start.config(state=DISABLED, relief=SUNKEN)
163 self.__stop.config(state=NORMAL)
164 self.__step.config(state=DISABLED)
165 self.enterroot()
166 self.__running = 1
167 self.go()
168
169 def stop(self):
170 self.__stop.config(state=DISABLED)
171 self.__running = 0
172
173 def step(self):
174 self.__start.config(state=DISABLED)
175 self.__step.config(state=DISABLED, relief=SUNKEN)
176 self.enterroot()
177 self.__running = 0
178 self.dosomething()
Guido van Rossum06981c31997-01-31 18:58:12 +0000179
180 def go(self):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000181 if self.__running:
Guido van Rossum06981c31997-01-31 18:58:12 +0000182 self.__parent.after_idle(self.dosomething)
183 else:
184 self.__checking.config(text="Checking: none")
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000185 self.__start.config(state=NORMAL, relief=RAISED)
186 self.__step.config(state=NORMAL, relief=RAISED)
187
188 __busy = 0
Guido van Rossum06981c31997-01-31 18:58:12 +0000189
190 def dosomething(self):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000191 if self.__busy: return
192 self.__busy = 1
Guido van Rossum06981c31997-01-31 18:58:12 +0000193 if self.todo:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000194 l = self.__todo.selectedindices()
195 if l:
196 i = l[0]
197 else:
198 i = 0
199 self.__todo.list.select_set(i)
200 self.__todo.list.yview(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000201 url = self.__todo.items[i]
202 self.__checking.config(text="Checking: "+url)
203 self.__parent.update()
204 self.dopage(url)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000205 elif self.__extodo:
Guido van Rossum06981c31997-01-31 18:58:12 +0000206 # XXX Should have an indication of these in the todo window...
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000207 ##i = random.randint(0, len(self.__extodo)-1)
208 i = 0
Guido van Rossum06981c31997-01-31 18:58:12 +0000209 url = self.__extodo[i]
210 del self.__extodo[i]
211 self.__checking.config(text="Checking: "+url)
212 self.__parent.update()
213 self.checkextpage(url)
214 else:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000215 self.stop()
216 self.__busy = 0
Guido van Rossum06981c31997-01-31 18:58:12 +0000217 self.go()
218
219 def showinfo(self, url):
220 d = self.__details
221 d.clear()
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000222 d.write("URL: %s\n" % url)
Guido van Rossum06981c31997-01-31 18:58:12 +0000223 if self.bad.has_key(url):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000224 d.write("Error: %s\n" % str(self.bad[url]))
225 if url in self.roots:
226 d.write("Note: This is a root URL\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000227 if self.done.has_key(url):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000228 d.write("Status: checked\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000229 o = self.done[url]
230 elif self.todo.has_key(url):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000231 d.write("Status: to check\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000232 o = self.todo[url]
233 elif self.ext.has_key(url):
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000234 d.write("Status: off site\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000235 o = self.ext[url]
236 else:
237 d.write("Status: unknown (!)\n")
238 o = []
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000239 self.__mp.showpanel("Details")
Guido van Rossum06981c31997-01-31 18:58:12 +0000240 for source, rawlink in o:
241 d.write("Origin: %s" % source)
242 if rawlink != url:
243 d.write(" (%s)" % rawlink)
244 d.write("\n")
Guido van Rossum06981c31997-01-31 18:58:12 +0000245
246 def newstatus(self):
247 self.__status.config(text="Status: "+self.status()[1:-1])
248 self.__parent.update()
249
250 def setbad(self, url, msg):
251 webchecker.Checker.setbad(self, url, msg)
252 self.__bad.insert(url)
253 self.newstatus()
254
255 def setgood(self, url):
256 webchecker.Checker.setgood(self, url)
257 self.__bad.remove(url)
258 self.newstatus()
259
260 def newextlink(self, url, origin):
261 webchecker.Checker.newextlink(self, url, origin)
262 self.__extodo.append(url)
263 self.__ext.insert(url)
264 self.newstatus()
265
266 def newintlink(self, url, origin):
267 webchecker.Checker.newintlink(self, url, origin)
268 if self.done.has_key(url):
269 self.__done.insert(url)
270 elif self.todo.has_key(url):
271 self.__todo.insert(url)
272 self.newstatus()
273
274 def markdone(self, url):
275 webchecker.Checker.markdone(self, url)
276 self.__done.insert(url)
277 self.__todo.remove(url)
278 self.newstatus()
279
280
281class ListPanel:
282
283 def __init__(self, mp, name, showinfo=None):
284 self.mp = mp
285 self.name = name
286 self.showinfo = showinfo
287 self.panel = mp.addpanel(name)
288 self.list, self.frame = tktools.make_list_box(
289 self.panel, width=60, height=5)
290 self.list.config(exportselection=0)
291 if showinfo:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000292 self.list.bind('<Double-Button-1>', self.doubleclick)
Guido van Rossum06981c31997-01-31 18:58:12 +0000293 self.items = []
294
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000295 def doubleclick(self, event):
296 l = self.selectedindices()
297 if l:
298 self.showinfo(self.list.get(l[0]))
299
300 def selectedindices(self):
301 l = self.list.curselection()
302 if not l: return []
303 return map(string.atoi, l)
Guido van Rossum06981c31997-01-31 18:58:12 +0000304
305 def insert(self, url):
306 if url not in self.items:
307 if not self.items:
308 self.mp.showpanel(self.name)
309 # (I tried sorting alphabetically, but the display is too jumpy)
310 i = len(self.items)
311 self.list.insert(i, url)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000312## self.list.yview(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000313 self.items.insert(i, url)
314
315 def remove(self, url):
316 try:
317 i = self.items.index(url)
318 except (ValueError, IndexError):
319 pass
320 else:
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000321 was_selected = i in self.selectedindices()
Guido van Rossum06981c31997-01-31 18:58:12 +0000322 self.list.delete(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000323 del self.items[i]
324 if not self.items:
325 self.mp.hidepanel(self.name)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000326 elif was_selected:
327 if i >= len(self.items):
328 i = len(self.items) - 1
329 self.list.select_set(i)
Guido van Rossum06981c31997-01-31 18:58:12 +0000330
331
332class LogPanel:
333
334 def __init__(self, mp, name):
335 self.mp = mp
336 self.name = name
337 self.panel = mp.addpanel(name)
338 self.text, self.frame = tktools.make_text_box(self.panel, height=10)
339 self.text.config(wrap=NONE)
340
341 def clear(self):
342 self.text.delete("1.0", END)
343 self.text.yview("1.0")
344
345 def write(self, s):
346 self.text.insert(END, s)
Guido van Rossum4f6ecda1997-02-01 05:17:29 +0000347 if '\n' in s:
348 self.text.yview(END)
349 self.panel.update()
Guido van Rossum06981c31997-01-31 18:58:12 +0000350
351
352class MultiPanel:
353
354 def __init__(self, parent):
355 self.parent = parent
356 self.frame = Frame(self.parent)
357 self.frame.pack(expand=1, fill=BOTH)
358 self.topframe = Frame(self.frame, borderwidth=2, relief=RAISED)
359 self.topframe.pack(fill=X)
360 self.botframe = Frame(self.frame)
361 self.botframe.pack(expand=1, fill=BOTH)
362 self.panelnames = []
363 self.panels = {}
364
365 def addpanel(self, name, on=0):
366 v = StringVar()
367 if on:
368 v.set(name)
369 else:
370 v.set("")
371 check = Checkbutton(self.topframe, text=name,
372 offvalue="", onvalue=name, variable=v,
373 command=self.checkpanel)
374 check.pack(side=LEFT)
375 panel = Frame(self.botframe)
376 label = Label(panel, text=name, borderwidth=2, relief=RAISED, anchor=W)
377 label.pack(side=TOP, fill=X)
378 t = v, check, panel
379 self.panelnames.append(name)
380 self.panels[name] = t
381 if on:
382 panel.pack(expand=1, fill=BOTH)
383 return panel
384
385 def showpanel(self, name):
386 v, check, panel = self.panels[name]
387 v.set(name)
388 panel.pack(expand=1, fill=BOTH)
389
390 def hidepanel(self, name):
391 v, check, panel = self.panels[name]
392 v.set("")
393 panel.pack_forget()
394
395 def checkpanel(self):
396 for name in self.panelnames:
397 v, check, panel = self.panels[name]
398 panel.pack_forget()
399 for name in self.panelnames:
400 v, check, panel = self.panels[name]
401 if v.get():
402 panel.pack(expand=1, fill=BOTH)
403
404
405if __name__ == '__main__':
406 main()