PageRenderTime 704ms CodeModel.GetById 181ms app.highlight 271ms RepoModel.GetById 186ms app.codeStats 61ms

/Tools/webchecker/wcgui.py

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