#! /usr/bin/env python | |
"""GUI interface to webchecker. | |
This works as a Grail applet too! E.g. | |
<APPLET CODE=wcgui.py NAME=CheckerWindow></APPLET> | |
Checkpoints are not (yet??? ever???) supported. | |
User interface: | |
Enter a root to check in the text entry box. To enter more than one root, | |
enter them one at a time and press <Return> for each one. | |
Command buttons Start, Stop and "Check one" govern the checking process in | |
the obvious way. Start and "Check one" also enter the root from the text | |
entry box if one is present. There's also a check box (enabled by default) | |
to decide whether actually to follow external links (since this can slow | |
the checking down considerably). Finally there's a Quit button. | |
A series of checkbuttons determines whether the corresponding output panel | |
is shown. List panels are also automatically shown or hidden when their | |
status changes between empty to non-empty. There are six panels: | |
Log -- raw output from the checker (-v, -q affect this) | |
To check -- links discovered but not yet checked | |
Checked -- links that have been checked | |
Bad links -- links that failed upon checking | |
Errors -- pages containing at least one bad link | |
Details -- details about one URL; double click on a URL in any of | |
the above list panels (not in Log) will show details | |
for that URL | |
Use your window manager's Close command to quit. | |
Command line options: | |
-m bytes -- skip HTML pages larger than this size (default %(MAXPAGE)d) | |
-q -- quiet operation (also suppresses external links report) | |
-v -- verbose operation; repeating -v will increase verbosity | |
-t root -- specify root dir which should be treated as internal (can repeat) | |
-a -- don't check name anchors | |
Command line arguments: | |
rooturl -- URL to start checking | |
(default %(DEFROOT)s) | |
XXX The command line options (-m, -q, -v) should be GUI accessible. | |
XXX The roots should be visible as a list (?). | |
XXX The multipanel user interface is clumsy. | |
""" | |
# ' Emacs bait | |
import sys | |
import getopt | |
from Tkinter import * | |
import tktools | |
import webchecker | |
def main(): | |
try: | |
opts, args = getopt.getopt(sys.argv[1:], 't:m:qva') | |
except getopt.error, msg: | |
sys.stdout = sys.stderr | |
print msg | |
print __doc__%vars(webchecker) | |
sys.exit(2) | |
webchecker.verbose = webchecker.VERBOSE | |
webchecker.nonames = webchecker.NONAMES | |
webchecker.maxpage = webchecker.MAXPAGE | |
extra_roots = [] | |
for o, a in opts: | |
if o == '-m': | |
webchecker.maxpage = int(a) | |
if o == '-q': | |
webchecker.verbose = 0 | |
if o == '-v': | |
webchecker.verbose = webchecker.verbose + 1 | |
if o == '-t': | |
extra_roots.append(a) | |
if o == '-a': | |
webchecker.nonames = not webchecker.nonames | |
root = Tk(className='Webchecker') | |
root.protocol("WM_DELETE_WINDOW", root.quit) | |
c = CheckerWindow(root) | |
c.setflags(verbose=webchecker.verbose, maxpage=webchecker.maxpage, | |
nonames=webchecker.nonames) | |
if args: | |
for arg in args[:-1]: | |
c.addroot(arg) | |
c.suggestroot(args[-1]) | |
# Usually conditioned on whether external links | |
# will be checked, but since that's not a command | |
# line option, just toss them in. | |
for url_root in extra_roots: | |
# Make sure it's terminated by a slash, | |
# so that addroot doesn't discard the last | |
# directory component. | |
if url_root[-1] != "/": | |
url_root = url_root + "/" | |
c.addroot(url_root, add_to_do = 0) | |
root.mainloop() | |
class CheckerWindow(webchecker.Checker): | |
def __init__(self, parent, root=webchecker.DEFROOT): | |
self.__parent = parent | |
self.__topcontrols = Frame(parent) | |
self.__topcontrols.pack(side=TOP, fill=X) | |
self.__label = Label(self.__topcontrols, text="Root URL:") | |
self.__label.pack(side=LEFT) | |
self.__rootentry = Entry(self.__topcontrols, width=60) | |
self.__rootentry.pack(side=LEFT) | |
self.__rootentry.bind('<Return>', self.enterroot) | |
self.__rootentry.focus_set() | |
self.__controls = Frame(parent) | |
self.__controls.pack(side=TOP, fill=X) | |
self.__running = 0 | |
self.__start = Button(self.__controls, text="Run", command=self.start) | |
self.__start.pack(side=LEFT) | |
self.__stop = Button(self.__controls, text="Stop", command=self.stop, | |
state=DISABLED) | |
self.__stop.pack(side=LEFT) | |
self.__step = Button(self.__controls, text="Check one", | |
command=self.step) | |
self.__step.pack(side=LEFT) | |
self.__cv = BooleanVar(parent) | |
self.__cv.set(self.checkext) | |
self.__checkext = Checkbutton(self.__controls, variable=self.__cv, | |
command=self.update_checkext, | |
text="Check nonlocal links",) | |
self.__checkext.pack(side=LEFT) | |
self.__reset = Button(self.__controls, text="Start over", command=self.reset) | |
self.__reset.pack(side=LEFT) | |
if __name__ == '__main__': # No Quit button under Grail! | |
self.__quit = Button(self.__controls, text="Quit", | |
command=self.__parent.quit) | |
self.__quit.pack(side=RIGHT) | |
self.__status = Label(parent, text="Status: initial", anchor=W) | |
self.__status.pack(side=TOP, fill=X) | |
self.__checking = Label(parent, text="Idle", anchor=W) | |
self.__checking.pack(side=TOP, fill=X) | |
self.__mp = mp = MultiPanel(parent) | |
sys.stdout = self.__log = LogPanel(mp, "Log") | |
self.__todo = ListPanel(mp, "To check", self, self.showinfo) | |
self.__done = ListPanel(mp, "Checked", self, self.showinfo) | |
self.__bad = ListPanel(mp, "Bad links", self, self.showinfo) | |
self.__errors = ListPanel(mp, "Pages w/ bad links", self, self.showinfo) | |
self.__details = LogPanel(mp, "Details") | |
self.root_seed = None | |
webchecker.Checker.__init__(self) | |
if root: | |
root = str(root).strip() | |
if root: | |
self.suggestroot(root) | |
self.newstatus() | |
def reset(self): | |
webchecker.Checker.reset(self) | |
for p in self.__todo, self.__done, self.__bad, self.__errors: | |
p.clear() | |
if self.root_seed: | |
self.suggestroot(self.root_seed) | |
def suggestroot(self, root): | |
self.__rootentry.delete(0, END) | |
self.__rootentry.insert(END, root) | |
self.__rootentry.select_range(0, END) | |
self.root_seed = root | |
def enterroot(self, event=None): | |
root = self.__rootentry.get() | |
root = root.strip() | |
if root: | |
self.__checking.config(text="Adding root "+root) | |
self.__checking.update_idletasks() | |
self.addroot(root) | |
self.__checking.config(text="Idle") | |
try: | |
i = self.__todo.items.index(root) | |
except (ValueError, IndexError): | |
pass | |
else: | |
self.__todo.list.select_clear(0, END) | |
self.__todo.list.select_set(i) | |
self.__todo.list.yview(i) | |
self.__rootentry.delete(0, END) | |
def start(self): | |
self.__start.config(state=DISABLED, relief=SUNKEN) | |
self.__stop.config(state=NORMAL) | |
self.__step.config(state=DISABLED) | |
self.enterroot() | |
self.__running = 1 | |
self.go() | |
def stop(self): | |
self.__stop.config(state=DISABLED, relief=SUNKEN) | |
self.__running = 0 | |
def step(self): | |
self.__start.config(state=DISABLED) | |
self.__step.config(state=DISABLED, relief=SUNKEN) | |
self.enterroot() | |
self.__running = 0 | |
self.dosomething() | |
def go(self): | |
if self.__running: | |
self.__parent.after_idle(self.dosomething) | |
else: | |
self.__checking.config(text="Idle") | |
self.__start.config(state=NORMAL, relief=RAISED) | |
self.__stop.config(state=DISABLED, relief=RAISED) | |
self.__step.config(state=NORMAL, relief=RAISED) | |
__busy = 0 | |
def dosomething(self): | |
if self.__busy: return | |
self.__busy = 1 | |
if self.todo: | |
l = self.__todo.selectedindices() | |
if l: | |
i = l[0] | |
else: | |
i = 0 | |
self.__todo.list.select_set(i) | |
self.__todo.list.yview(i) | |
url = self.__todo.items[i] | |
self.__checking.config(text="Checking "+self.format_url(url)) | |
self.__parent.update() | |
self.dopage(url) | |
else: | |
self.stop() | |
self.__busy = 0 | |
self.go() | |
def showinfo(self, url): | |
d = self.__details | |
d.clear() | |
d.put("URL: %s\n" % self.format_url(url)) | |
if self.bad.has_key(url): | |
d.put("Error: %s\n" % str(self.bad[url])) | |
if url in self.roots: | |
d.put("Note: This is a root URL\n") | |
if self.done.has_key(url): | |
d.put("Status: checked\n") | |
o = self.done[url] | |
elif self.todo.has_key(url): | |
d.put("Status: to check\n") | |
o = self.todo[url] | |
else: | |
d.put("Status: unknown (!)\n") | |
o = [] | |
if (not url[1]) and self.errors.has_key(url[0]): | |
d.put("Bad links from this page:\n") | |
for triple in self.errors[url[0]]: | |
link, rawlink, msg = triple | |
d.put(" HREF %s" % self.format_url(link)) | |
if self.format_url(link) != rawlink: d.put(" (%s)" %rawlink) | |
d.put("\n") | |
d.put(" error %s\n" % str(msg)) | |
self.__mp.showpanel("Details") | |
for source, rawlink in o: | |
d.put("Origin: %s" % source) | |
if rawlink != self.format_url(url): | |
d.put(" (%s)" % rawlink) | |
d.put("\n") | |
d.text.yview("1.0") | |
def setbad(self, url, msg): | |
webchecker.Checker.setbad(self, url, msg) | |
self.__bad.insert(url) | |
self.newstatus() | |
def setgood(self, url): | |
webchecker.Checker.setgood(self, url) | |
self.__bad.remove(url) | |
self.newstatus() | |
def newlink(self, url, origin): | |
webchecker.Checker.newlink(self, url, origin) | |
if self.done.has_key(url): | |
self.__done.insert(url) | |
elif self.todo.has_key(url): | |
self.__todo.insert(url) | |
self.newstatus() | |
def markdone(self, url): | |
webchecker.Checker.markdone(self, url) | |
self.__done.insert(url) | |
self.__todo.remove(url) | |
self.newstatus() | |
def seterror(self, url, triple): | |
webchecker.Checker.seterror(self, url, triple) | |
self.__errors.insert((url, '')) | |
self.newstatus() | |
def newstatus(self): | |
self.__status.config(text="Status: "+self.status()) | |
self.__parent.update() | |
def update_checkext(self): | |
self.checkext = self.__cv.get() | |
class ListPanel: | |
def __init__(self, mp, name, checker, showinfo=None): | |
self.mp = mp | |
self.name = name | |
self.showinfo = showinfo | |
self.checker = checker | |
self.panel = mp.addpanel(name) | |
self.list, self.frame = tktools.make_list_box( | |
self.panel, width=60, height=5) | |
self.list.config(exportselection=0) | |
if showinfo: | |
self.list.bind('<Double-Button-1>', self.doubleclick) | |
self.items = [] | |
def clear(self): | |
self.items = [] | |
self.list.delete(0, END) | |
self.mp.hidepanel(self.name) | |
def doubleclick(self, event): | |
l = self.selectedindices() | |
if l: | |
self.showinfo(self.items[l[0]]) | |
def selectedindices(self): | |
l = self.list.curselection() | |
if not l: return [] | |
return map(int, l) | |
def insert(self, url): | |
if url not in self.items: | |
if not self.items: | |
self.mp.showpanel(self.name) | |
# (I tried sorting alphabetically, but the display is too jumpy) | |
i = len(self.items) | |
self.list.insert(i, self.checker.format_url(url)) | |
self.list.yview(i) | |
self.items.insert(i, url) | |
def remove(self, url): | |
try: | |
i = self.items.index(url) | |
except (ValueError, IndexError): | |
pass | |
else: | |
was_selected = i in self.selectedindices() | |
self.list.delete(i) | |
del self.items[i] | |
if not self.items: | |
self.mp.hidepanel(self.name) | |
elif was_selected: | |
if i >= len(self.items): | |
i = len(self.items) - 1 | |
self.list.select_set(i) | |
class LogPanel: | |
def __init__(self, mp, name): | |
self.mp = mp | |
self.name = name | |
self.panel = mp.addpanel(name) | |
self.text, self.frame = tktools.make_text_box(self.panel, height=10) | |
self.text.config(wrap=NONE) | |
def clear(self): | |
self.text.delete("1.0", END) | |
self.text.yview("1.0") | |
def put(self, s): | |
self.text.insert(END, s) | |
if '\n' in s: | |
self.text.yview(END) | |
def write(self, s): | |
self.text.insert(END, s) | |
if '\n' in s: | |
self.text.yview(END) | |
self.panel.update() | |
class MultiPanel: | |
def __init__(self, parent): | |
self.parent = parent | |
self.frame = Frame(self.parent) | |
self.frame.pack(expand=1, fill=BOTH) | |
self.topframe = Frame(self.frame, borderwidth=2, relief=RAISED) | |
self.topframe.pack(fill=X) | |
self.botframe = Frame(self.frame) | |
self.botframe.pack(expand=1, fill=BOTH) | |
self.panelnames = [] | |
self.panels = {} | |
def addpanel(self, name, on=0): | |
v = StringVar(self.parent) | |
if on: | |
v.set(name) | |
else: | |
v.set("") | |
check = Checkbutton(self.topframe, text=name, | |
offvalue="", onvalue=name, variable=v, | |
command=self.checkpanel) | |
check.pack(side=LEFT) | |
panel = Frame(self.botframe) | |
label = Label(panel, text=name, borderwidth=2, relief=RAISED, anchor=W) | |
label.pack(side=TOP, fill=X) | |
t = v, check, panel | |
self.panelnames.append(name) | |
self.panels[name] = t | |
if on: | |
panel.pack(expand=1, fill=BOTH) | |
return panel | |
def showpanel(self, name): | |
v, check, panel = self.panels[name] | |
v.set(name) | |
panel.pack(expand=1, fill=BOTH) | |
def hidepanel(self, name): | |
v, check, panel = self.panels[name] | |
v.set("") | |
panel.pack_forget() | |
def checkpanel(self): | |
for name in self.panelnames: | |
v, check, panel = self.panels[name] | |
panel.pack_forget() | |
for name in self.panelnames: | |
v, check, panel = self.panels[name] | |
if v.get(): | |
panel.pack(expand=1, fill=BOTH) | |
if __name__ == '__main__': | |
main() |