#! /depot/sundry/plat/bin/python1.4 """Interactive FAQ project. XXX TO DO - use cookies to keep Name/email the same - explanation of editing somewhere - various embellishments, GIFs, crosslinks, hints, etc. - create new sections - rearrange entries - delete entries - log changes - send email on changes - optional staging of entries until reviewed? - review revision log and older versions - freeze entries - username/password for editors - Change references to other Q's and whole sections - Browse should display menu of 7 sections & let you pick (or frontpage should have the option to browse a section or all) - support adding annotations, too """ import cgi, string, os NAMEPAT = "faq??.???.htp" NAMEREG = "^faq\([0-9][0-9]\)\.\([0-9][0-9][0-9]\)\.htp$" class FAQServer: def __init__(self): pass def main(self): self.form = cgi.FieldStorage() req = self.req or 'frontpage' try: method = getattr(self, 'do_%s' % req) except AttributeError: print "Unrecognized request type", req else: method() KEYS = ['req', 'query', 'name', 'text', 'commit', 'title', 'author', 'email', 'log', 'section', 'number', 'add'] def __getattr__(self, key): if key not in self.KEYS: raise AttributeError try: item = self.form[key] return item.value except KeyError: return '' def do_frontpage(self): print """ Python FAQ (alpha 1)

Python FAQ Front Page

Search the FAQ

Disclaimer: these pages are intended to be edited by anyone. Please exercise discretion when editing, don't be rude, etc. """ def do_index(self): print """ Python FAQ Index

Python FAQ Index

""" names = os.listdir(os.curdir) names.sort() section = None for name in names: headers, text = self.read(name) if headers: title = headers['title'] i = string.find(title, '.') nsec = title[:i] if nsec != section: if section: print """

  • Add new entry (at this point) """ % section section = nsec print "

    Section %s

    " % section print " """ % section else: print "No FAQ entries?!?!" def do_show(self): name = self.name headers, text = self.read(name) if not headers: print "Invalid file name", name return self.show(name, headers['title'], text, 1) def do_all(self): print """ Python FAQ

    Python FAQ


    """ names = os.listdir(os.curdir) names.sort() section = None for name in names: headers, text = self.read(name) if headers: title = headers['title'] i = string.find(title, '.') nsec = title[:i] if nsec != section: section = nsec print "

    Section %s

    " % section print "
    " self.show(name, title, text, 1) n = n+1 if not section: print "No FAQ entries?!?!" def do_roulette(self): import whrandom print """ Python FAQ Roulette

    Python FAQ Roulette

    Please check the correctness of the entry below. If you find any problems, please edit the entry.


    """ names = os.listdir(os.curdir) while names: name = whrandom.choice(names) headers, text = self.read(name) if headers: self.show(name, headers['title'], text, 1) print '

    Show another one' break else: names.remove(name) else: print "No FAQ entries?!?!" def do_recent(self): import fnmatch, stat names = os.listdir(os.curdir) now = time.time() list = [] for name in names: if not fnmatch.fnmatch(name, NAMEPAT): continue try: st = os.stat(name) except os.error: continue tuple = (st[stat.ST_MTIME], name) list.append(tuple) list.sort() list.reverse() print """ Python FAQ, Most Recently Modified First

    Python FAQ, Most Recently Modified First


    """ n = 0 for (mtime, name) in list: headers, text = self.read(name) if headers: self.show(name, headers['title'], text, 1) n = n+1 if not n: print "No FAQ entries?!?!" def do_query(self): import regex print "Python FAQ Query Results" print "

    Python FAQ Query Results

    " query = self.query if not query: print "No query string" return p = regex.compile(query, regex.casefold) names = os.listdir(os.curdir) names.sort() print "
    " n = 0 for name in names: headers, text = self.read(name) if headers: title = headers['title'] if p.search(title) >= 0 or p.search(text) >= 0: self.show(name, title, text, 1) n = n+1 if not n: print "No hits." def do_add(self): section = self.section if not section: print """ How to add a new FAQ entry

    How to add a new FAQ entry

    Go to the FAQ index and click on the "Add new entry" link at the end of the section to which you want to add the entry. """ return try: nsec = string.atoi(section) except ValueError: print "Bad section number", nsec names = os.listdir(os.curdir) max = 0 import regex prog = regex.compile(NAMEREG) for name in names: if prog.match(name) >= 0: s1, s2 = prog.group(1, 2) n1, n2 = string.atoi(s1), string.atoi(s2) if n1 == nsec: if n2 > max: max = n2 if not max: print "Can't add new sections yet." return num = max+1 name = "faq%02d.%03d.htp" % (nsec, num) self.name = name self.add = "yes" self.number = str(num) self.do_edit() def do_edit(self): name = self.name headers, text = self.read(name) if not headers: print "Invalid file name", name return print """ Python FAQ Edit Form

    Python FAQ Edit Form

    """ title = headers['title'] print "
    " self.showedit(name, title, text) if self.add: print """ """ % (self.add, self.section, self.number) print """

    """ % name self.show(name, title, text) def do_review(self): if self.commit: self.checkin() return name = self.name text = self.text title = self.title headers, oldtext = self.read(name) if not headers: print "Invalid file name", name return print """ Python FAQ Review Form

    Python FAQ Review Form


    """ self.show(name, title, text) print "
    " if self.log and self.author and '@' in self.email: print """ Click this button to commit the change.


    """ else: print """ To commit this change, please enter your name, email and a log message in the form below.


    """ self.showedit(name, title, text) if self.add: print """ """ % (self.add, self.section, self.number) print """


    """ % name def checkin(self): import regsub, time, tempfile name = self.name headers, oldtext = self.read(name) if not headers: print "Invalid file name", name return text = self.text title = self.title author = self.author email = self.email log = self.log text = regsub.gsub("\r\n", "\n", text) log = regsub.gsub("\r\n", "\n", log) author = string.join(string.split(author)) email = string.join(string.split(email)) title = string.join(string.split(title)) oldtitle = headers['title'] oldtitle = string.join(string.split(oldtitle)) text = string.strip(text) oldtext = string.strip(oldtext) if text == oldtext and title == oldtitle: print "No changes." # XXX Should exit more ceremoniously return # Check that the FAQ entry number didn't change if string.split(title)[:1] != string.split(oldtitle)[:1]: print "Don't change the FAQ entry number please." # XXX Should exit more ceremoniously return remhost = os.environ["REMOTE_HOST"] remaddr = os.environ["REMOTE_ADDR"] try: os.unlink(name + "~") except os.error: pass try: os.rename(name, name + "~") except os.error: pass try: os.unlink(name) except os.error: pass try: f = open(name, "w") except IOError, msg: print "Can't open", name, "for writing:", cgi.escape(str(msg)) # XXX Should exit more ceremoniously return now = time.ctime(time.time()) f.write("Title: %s\n" % title) f.write("Last-Changed-Date: %s\n" % now) f.write("Last-Changed-Author: %s\n" % author) f.write("Last-Changed-Email: %s\n" % email) f.write("Last-Changed-Remote-Host: %s\n" % remhost) f.write("Last-Changed-Remote-Address: %s\n" % remaddr) keys = headers.keys() keys.sort() keys.remove('title') for key in keys: if key[:13] != 'last-changed-': f.write("%s: %s\n" % (string.capwords(key, '-'), headers[key])) f.write("\n") f.write(text) f.write("\n") f.close() tfn = tempfile.mktemp() f = open(tfn, "w") f.write("Last-Changed-Date: %s\n" % now) f.write("Last-Changed-Author: %s\n" % author) f.write("Last-Changed-Email: %s\n" % email) f.write("Last-Changed-Remote-Host: %s\n" % remhost) f.write("Last-Changed-Remote-Address: %s\n" % remaddr) f.write("\n") f.write(log) f.write("\n") f.close() p = os.popen(""" /depot/gnu/plat/bin/rcs -l %s &1 /depot/gnu/plat/bin/ci -u %s <%s 2>&1 rm -f %s """ % (name, name, tfn, tfn)) output = p.read() sts = p.close() if not sts: print """ Python FAQ Entry Edited

    Python FAQ Entry Edited


    """ self.show(name, title, text, 1) if output: print "
    %s
    " % cgi.escape(output) else: print """

    Python FAQ Entry Commit Failed

    Exit status 0x%04x """ % sts if output: print "
    %s
    " % cgi.escape(output) def showedit(self, name, title, text): print """ Title:
    Please provide the following information for logging purposes:
    Name : Email: Log message (reason for the change):
    """ % (self.author, self.email, self.log) def showheaders(self, headers): print "" def read(self, name): import fnmatch, rfc822 if not fnmatch.fnmatch(name, NAMEPAT): return None, None if self.add: try: fname = "faq%02d.%03d.htp" % (string.atoi(self.section), string.atoi(self.number)) except ValueError: return None, None if fname != name: return None, None headers = {'title': "%s.%s. " % (self.section, self.number)} text = "" return headers, text f = open(name) headers = rfc822.Message(f) text = f.read() f.close() return headers, text def show(self, name, title, text, edit=0): # XXX Should put tags around recognizable URLs # XXX Should also turn "see section N" into hyperlinks print "

    %s

    " % cgi.escape(title) pre = 0 for line in string.split(text, '\n'): if not string.strip(line): if pre: print '' pre = 0 else: print '

    ' else: if line == string.lstrip(line): # I.e., no leading whitespace if pre: print '' pre = 0 else: if not pre: print '

    '
    			pre = 1
    		print cgi.escape(line)
    	if pre:
    	    print '
    ' pre = 0 print '

    ' if edit: print 'Edit this entry' %name print '

    ' print "


    " print "Content-type: text/html\n" dt = 0 try: import time t1 = time.time() x = FAQServer() x.main() t2 = time.time() dt = t2-t1 except: print "
    Sorry, an error occurred" cgi.print_exception() print "

    (time = %s seconds)" % str(round(dt, 3))