Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 1 | #! /usr/bin/env python |
| 2 | |
| 3 | """GUI interface to webchecker. |
| 4 | |
| 5 | This works as a Grail applet too! E.g. |
| 6 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 7 | <APPLET CODE=wcgui.py NAME=CheckerWindow></APPLET> |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 8 | |
| 9 | Checkpoints are not (yet?) supported. |
| 10 | |
| 11 | User interface: |
| 12 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 13 | Enter a root to check in the text entry box. To enter more than one root, |
| 14 | enter each root and press <Return>. |
| 15 | |
| 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. |
| 19 | |
| 20 | A series of checkbuttons determines whether the corresponding output panel |
| 21 | is shown. List panels are also automatically shown or hidden when their |
| 22 | status changes between empty to non-empty. There are six panels: |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 23 | |
| 24 | Log -- raw output from the checker (-v, -q affect this) |
| 25 | To check -- local links discovered but not yet checked |
| 26 | Off site -- links discovered that point off site |
| 27 | Checked -- local links that have been checked |
| 28 | Bad links -- links that failed upon checking |
| 29 | Details -- 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 | |
| 32 | XXX There ought to be a list of pages known to contain at least one |
| 33 | bad link. |
| 34 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 35 | XXX The checking of off site links should be made more similar to the |
| 36 | checking of local links (even if they are checked after all local links are |
| 37 | checked). |
| 38 | |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 39 | Use your window manager's Close command to quit. |
| 40 | |
| 41 | Command 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 47 | |
| 48 | Command line arguments: |
| 49 | |
| 50 | rooturl -- URL to start checking |
| 51 | (default %(DEFROOT)s) |
| 52 | |
| 53 | XXX The command line options should all be GUI accessible. |
| 54 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 55 | XXX The roots should be visible as a list (?). |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 56 | |
| 57 | XXX The multipanel user interface is bogus. |
| 58 | |
| 59 | """ |
| 60 | |
| 61 | # ' Emacs bait |
| 62 | |
| 63 | |
| 64 | import sys |
| 65 | import getopt |
| 66 | import string |
| 67 | from Tkinter import * |
| 68 | import tktools |
| 69 | import webchecker |
| 70 | import random |
| 71 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 72 | # Override some for a weaker platform |
| 73 | if sys.platform == 'mac': |
| 74 | webchecker.DEFROOT = "http://grail.cnri.reston.va.us/" |
| 75 | webchecker.MAXPAGE = 50000 |
| 76 | webchecker.verbose = 4 |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 77 | |
| 78 | def main(): |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 79 | try: |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 80 | opts, args = getopt.getopt(sys.argv[1:], 'm:qv') |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 81 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 93 | root = Tk(className='Webchecker') |
| 94 | root.protocol("WM_DELETE_WINDOW", root.quit) |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 95 | c = CheckerWindow(root) |
| 96 | if args: |
| 97 | for arg in args[:-1]: |
| 98 | c.addroot(arg) |
| 99 | c.suggestroot(args[-1]) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 100 | root.mainloop() |
| 101 | |
| 102 | |
| 103 | class CheckerWindow(webchecker.Checker): |
| 104 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 105 | def __init__(self, parent, root=webchecker.DEFROOT): |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 106 | self.__parent = parent |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 107 | self.__controls = Frame(parent) |
| 108 | self.__controls.pack(side=TOP, fill=X) |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 109 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 123 | 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 136 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 179 | |
| 180 | def go(self): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 181 | if self.__running: |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 182 | self.__parent.after_idle(self.dosomething) |
| 183 | else: |
| 184 | self.__checking.config(text="Checking: none") |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 185 | self.__start.config(state=NORMAL, relief=RAISED) |
| 186 | self.__step.config(state=NORMAL, relief=RAISED) |
| 187 | |
| 188 | __busy = 0 |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 189 | |
| 190 | def dosomething(self): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 191 | if self.__busy: return |
| 192 | self.__busy = 1 |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 193 | if self.todo: |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 194 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 201 | url = self.__todo.items[i] |
| 202 | self.__checking.config(text="Checking: "+url) |
| 203 | self.__parent.update() |
| 204 | self.dopage(url) |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 205 | elif self.__extodo: |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 206 | # XXX Should have an indication of these in the todo window... |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 207 | ##i = random.randint(0, len(self.__extodo)-1) |
| 208 | i = 0 |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 209 | 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 215 | self.stop() |
| 216 | self.__busy = 0 |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 217 | self.go() |
| 218 | |
| 219 | def showinfo(self, url): |
| 220 | d = self.__details |
| 221 | d.clear() |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 222 | d.write("URL: %s\n" % url) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 223 | if self.bad.has_key(url): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 224 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 227 | if self.done.has_key(url): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 228 | d.write("Status: checked\n") |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 229 | o = self.done[url] |
| 230 | elif self.todo.has_key(url): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 231 | d.write("Status: to check\n") |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 232 | o = self.todo[url] |
| 233 | elif self.ext.has_key(url): |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 234 | d.write("Status: off site\n") |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 235 | o = self.ext[url] |
| 236 | else: |
| 237 | d.write("Status: unknown (!)\n") |
| 238 | o = [] |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 239 | self.__mp.showpanel("Details") |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 240 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 245 | |
| 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 | |
| 281 | class 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 292 | self.list.bind('<Double-Button-1>', self.doubleclick) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 293 | self.items = [] |
| 294 | |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 295 | 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 Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 304 | |
| 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 312 | ## self.list.yview(i) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 313 | 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 321 | was_selected = i in self.selectedindices() |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 322 | self.list.delete(i) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 323 | del self.items[i] |
| 324 | if not self.items: |
| 325 | self.mp.hidepanel(self.name) |
Guido van Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 326 | elif was_selected: |
| 327 | if i >= len(self.items): |
| 328 | i = len(self.items) - 1 |
| 329 | self.list.select_set(i) |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 330 | |
| 331 | |
| 332 | class 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 Rossum | 4f6ecda | 1997-02-01 05:17:29 +0000 | [diff] [blame^] | 347 | if '\n' in s: |
| 348 | self.text.yview(END) |
| 349 | self.panel.update() |
Guido van Rossum | 06981c3 | 1997-01-31 18:58:12 +0000 | [diff] [blame] | 350 | |
| 351 | |
| 352 | class 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 | |
| 405 | if __name__ == '__main__': |
| 406 | main() |