#! /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 """
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 "
"
print '
%s' % (
name, cgi.escape(title))
if 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 "
""" % 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 "
""" % 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 "
"
keys = map(string.lower, headers.keys())
keys.sort()
for key in keys:
print "
%s: %s" % (string.capwords(key, '-'),
headers[key] or '')
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 "